205 lines
8.6 KiB
Markdown
205 lines
8.6 KiB
Markdown
# 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.
|