# Agent Self-Context: Know Thyself

## Problem

When the agent is asked about itself — its profile, contacts, relays, or recent notes — it has no idea. The context template currently injects **administrator** identity/profile/contacts/relays/notes into the system prompt, but nothing equivalent for the **agent's own** Nostr identity beyond a bare pubkey+npub from `agent_identity`.

The agent publishes its own kind 0 (profile), kind 3 (contacts), kind 10002 (relays), and kind 1 (notes) at startup, but never subscribes to or caches those events for self-awareness.

## Solution Overview

Mirror the admin context pattern for the agent itself:

1. **Subscribe** to the agent's own kind 0/3/10002/1 events from relays
2. **Cache** them in `nostr_handler.c` (same pattern as `g_admin_kind0_json` etc.)
3. **Expose** them via new `nostr_handler_get_agent_*` API functions
4. **Create context tools** that format the cached data into context blocks
5. **Add to context template** so the agent always knows about itself
6. **Add callable tool aliases** like `my_kind0_profile` for on-demand use

```mermaid
flowchart TD
    A[Startup: publish kind 0/3/10002/1] --> B[Subscribe to own events]
    B --> C[Cache in nostr_handler globals]
    C --> D[Context tools read cache]
    D --> E[Context template injects into system prompt]
    D --> F[LLM can call tools on-demand]
    
    subgraph Admin Context - existing
        G[g_admin_kind0_json]
        H[g_admin_kind3 contacts]
        I[g_admin_kind10002_json]
        J[g_admin_kind1_notes]
    end
    
    subgraph Agent Context - new
        K[g_agent_kind0_json]
        L[g_agent_kind3 contacts]
        M[g_agent_kind10002_json]
        N[g_agent_kind1_notes]
    end
```

## Detailed Changes

### 1. nostr_handler.c — Agent Self-Context Cache

Add new static globals mirroring the admin pattern:

```c
static char* g_agent_kind0_json = NULL;       // kind 0 profile JSON
static char* g_agent_kind10002_json = NULL;    // kind 10002 relay list JSON
static char** g_agent_kind3_contacts = NULL;   // kind 3 contact pubkeys
static int g_agent_kind3_contact_count = 0;
static admin_kind1_note_t* g_agent_kind1_notes = NULL;  // reuse struct
static int g_agent_kind1_note_count = 0;
```

### 2. nostr_handler.c — Agent Self-Context Subscription

Create `nostr_handler_subscribe_agent_context()` that subscribes to kinds 0, 3, 10002, 1 filtered by the agent's own pubkey. This is separate from the self-skills subscription (which handles 31123/31124/10123).

The callback `on_agent_context_event()` will parse and cache events using the same logic as `on_admin_context_event()`.

**Alternative considered:** Expanding `nostr_handler_subscribe_self_skills()` to include these kinds. Rejected because the self-skills sub has different EOSE handling and a callback for skill loading. Keeping them separate is cleaner.

### 3. nostr_handler.h — New API Functions

```c
int nostr_handler_subscribe_agent_context(void);
char* nostr_handler_get_agent_kind0_context(void);
char* nostr_handler_get_agent_kind3_context(void);
char* nostr_handler_get_agent_kind10002_context(void);
char* nostr_handler_get_agent_kind1_notes_context(void);
```

### 4. main.c — Call Agent Context Subscription at Startup

Add `nostr_handler_subscribe_agent_context()` call after admin context subscription, before self-skills subscription.

### 5. tool_agent.c — New Context Tools

Create four new tool functions following the exact pattern from `tool_admin.c`:

| Tool Name | Description | Data Source |
|-----------|-------------|-------------|
| `nostr_agent_profile` | Agent's kind 0 profile metadata | `nostr_handler_get_agent_kind0_context()` |
| `nostr_agent_contacts` | Agent's kind 3 contact list | `nostr_handler_get_agent_kind3_context()` |
| `nostr_agent_relays` | Agent's kind 10002 relay list | `nostr_handler_get_agent_kind10002_context()` |
| `nostr_agent_notes` | Agent's recent kind 1 notes | `nostr_handler_get_agent_kind1_notes_context()` |

Each returns a `content` field with markdown-formatted context, e.g.:
```
## Agent Kind 0 Profile (source: nostr kind 0)

Agent kind 0 profile content (JSON): {"name":"Didactyl Agent","display_name":"Didactyl",...}
```

### 6. tools_internal.h — Declare New Functions

```c
char* execute_nostr_agent_profile(tools_context_t* ctx, const char* args_json);
char* execute_nostr_agent_contacts(tools_context_t* ctx, const char* args_json);
char* execute_nostr_agent_relays(tools_context_t* ctx, const char* args_json);
char* execute_nostr_agent_notes(tools_context_t* ctx, const char* args_json);
```

### 7. tools_dispatch.c — Register + Aliases

Add dispatch entries:
```c
if (strcmp(tool_name, "nostr_agent_profile") == 0)  return execute_nostr_agent_profile(ctx, args_json);
if (strcmp(tool_name, "nostr_agent_contacts") == 0) return execute_nostr_agent_contacts(ctx, args_json);
if (strcmp(tool_name, "nostr_agent_relays") == 0)   return execute_nostr_agent_relays(ctx, args_json);
if (strcmp(tool_name, "nostr_agent_notes") == 0)    return execute_nostr_agent_notes(ctx, args_json);

// Friendly aliases
if (strcmp(tool_name, "my_kind0_profile") == 0) return execute_nostr_agent_profile(ctx, args_json);
if (strcmp(tool_name, "my_contacts") == 0)      return execute_nostr_agent_contacts(ctx, args_json);
if (strcmp(tool_name, "my_relays") == 0)         return execute_nostr_agent_relays(ctx, args_json);
if (strcmp(tool_name, "my_notes") == 0)          return execute_nostr_agent_notes(ctx, args_json);
```

### 8. tools_schema.c — OpenAI Function Schemas

Add 8 new tool schemas (4 canonical + 4 aliases), all with empty parameters (no-arg tools), following the pattern of `admin_identity`/`nostr_admin_profile` etc.

### 9. Context Template Update

Update the kind 31120 soul content in `config.jsonc` and `context_template.md` to add agent sections **after** admin sections:

```yaml
- section: admin_notes
  role: system
  tool: nostr_admin_notes
  skip_if_empty: true

# NEW: Agent self-context sections
- section: agent_identity
  role: system
  tool: agent_identity
  skip_if_empty: true

- section: agent_profile
  role: system
  tool: nostr_agent_profile
  skip_if_empty: true

- section: agent_contacts
  role: system
  tool: nostr_agent_contacts
  skip_if_empty: true

- section: agent_relays
  role: system
  tool: nostr_agent_relays
  skip_if_empty: true

- section: agent_notes
  role: system
  tool: nostr_agent_notes
  skip_if_empty: true

- section: tasks
  role: system
  tool: task_list
  skip_if_empty: true
```

### 10. nostr_handler.c Cleanup

Add cleanup for agent context globals in `nostr_handler_cleanup()`, mirroring `free_admin_context_locked()`.

## File Change Summary

| File | Change Type | Description |
|------|-------------|-------------|
| `src/nostr_handler.h` | Modify | Add 5 new function declarations |
| `src/nostr_handler.c` | Modify | Add agent context cache globals, subscription, event handler, getter functions, cleanup |
| `src/main.c` | Modify | Call `nostr_handler_subscribe_agent_context()` at startup |
| `src/tools/tool_agent.c` | Modify | Add 4 new context tool execute functions |
| `src/tools/tools_internal.h` | Modify | Declare 4 new execute functions |
| `src/tools/tools_dispatch.c` | Modify | Add 8 dispatch entries (4 tools + 4 aliases) |
| `src/tools/tools_schema.c` | Modify | Add 8 OpenAI function schemas |
| `config.jsonc` | Modify | Update kind 31120 template section |
| `config.jsonc.example` | Modify | Update kind 31120 template section |
| `context_template.md` | Modify | Add agent_* sections after admin_* sections |

## Context Token Impact

Each agent context section adds roughly the same token count as its admin counterpart:
- agent_identity: ~40 tokens (already exists, just adding to template)
- agent_profile: ~80-150 tokens (depends on profile richness)
- agent_contacts: ~50-200 tokens (depends on contact count)
- agent_relays: ~50-100 tokens
- agent_notes: ~100-300 tokens (depends on note count/length)

Total additional context: ~320-790 tokens. With `skip_if_empty: true`, empty sections cost 0 tokens.

## Design Decisions

1. **Separate subscription vs expanding self-skills sub**: Separate is cleaner — different EOSE semantics, different callback needs.
2. **Cache from relay vs read from config**: Cache from relay is more accurate (reflects what's actually published, not just what config says). The startup_events in config are the *intent*; the relay data is the *reality*.
3. **Alias naming**: `my_kind0_profile` matches the existing `my_npub`/`my_pubkey` pattern. Also adding `my_contacts`, `my_relays`, `my_notes` for consistency.
4. **No new config section needed**: The agent context subscription is unconditional — an agent should always know about itself. No `agent_context.enabled` toggle needed.
