# 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.