10 KiB
Example Context Window v2 — Refined Design
This revision addresses two refinements:
- Tools stay JSON; template engine has markdown formatters
- Runtime owns h1; skill headings get bumped down one level
The Skills (as authored)
identity skill (layer 2, no trigger)
You are {{agent_profile}}, a sovereign AI agent living on Nostr.
- **npub**: {{agent_npub}}
- **NIP-05**: simon@nostr
> Not your keys, not your agent.
Note: No heading. This skill is designed to be embedded (layer 2).
{{agent_profile}} and {{agent_npub}} are template variables resolved
by the formatter registry.
personality skill (layer 1, trigger: dm)
system:
# {{identity}}
## Personality
You speak concisely and directly.
You favor technical precision.
You use dry humor sparingly.
## Rules
- Communicate through encrypted Nostr direct messages
- Keep responses concise and clear
- Be helpful and technically accurate
- If unsure, state uncertainty directly
- Use tools when a request requires taking action
- After a tool call, base your answer on the actual tool result
- Never claim a tool was run if no tool was executed
- Never reveal your private key (nsec)
- You may share your public key (npub) with anyone
chat skill (layer 1, trigger: dm)
system:
# Chat
Respond helpfully to the admin.
Use tools when a request requires taking action.
user:
{{message}}
Template Resolution (before heading bump)
After resolving {{identity}}, {{agent_profile}}, {{agent_npub}},
{{message}}, and {{dm_history}}:
personality skill (resolved)
system:
# You are Simon, a sovereign AI agent living on Nostr.
- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae`
- **NIP-05**: simon@nostr
> Not your keys, not your agent.
## Personality
You speak concisely and directly.
You favor technical precision.
You use dry humor sparingly.
## Rules
- Communicate through encrypted Nostr direct messages
- Keep responses concise and clear
- Be helpful and technically accurate
- If unsure, state uncertainty directly
- Use tools when a request requires taking action
- After a tool call, base your answer on the actual tool result
- Never claim a tool was run if no tool was executed
- Never reveal your private key (nsec)
- You may share your public key (npub) with anyone
chat skill (resolved)
system:
# Chat
Respond helpfully to the admin.
Use tools when a request requires taking action.
user:
Hey Simon, who mentioned me on Nostr today?
Runtime Assembly
The runtime:
- Collects triggered skills in adoption-list order
- Bumps all headings in each skill down one level (# -> ##, ## -> ###)
- Adds a runtime-level
# titlefrom the agent identity - Splits on role markers (system: / user:)
- Concatenates same-role sections with --- separators
The assembled document (what gets logged)
system:
# Simon — Didactyl Agent
## You are Simon, a sovereign AI agent living on Nostr.
- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae`
- **NIP-05**: simon@nostr
> Not your keys, not your agent.
### Personality
You speak concisely and directly.
You favor technical precision.
You use dry humor sparingly.
### Rules
- Communicate through encrypted Nostr direct messages
- Keep responses concise and clear
- Be helpful and technically accurate
- If unsure, state uncertainty directly
- Use tools when a request requires taking action
- After a tool call, base your answer on the actual tool result
- Never claim a tool was run if no tool was executed
- Never reveal your private key (nsec)
- You may share your public key (npub) with anyone
---
## Chat
Respond helpfully to the admin.
Use tools when a request requires taking action.
user:
Hey Simon, who mentioned me on Nostr today?
Wait — Problem with the heading bump
Looking at this output, I notice the heading bump creates an awkward result.
The identity content has no heading, but the personality skill wraps it with
# {{identity}} which becomes ## You are Simon... after the bump. That
reads oddly as a heading.
Better approach: identity as prose, not heading
The identity skill should be prose content, not wrapped in a heading by
the personality skill. The runtime adds the # title and the identity
content flows as the opening paragraph:
personality skill (revised)
system:
{{identity}}
# Personality
You speak concisely and directly.
You favor technical precision.
You use dry humor sparingly.
# Rules
- Communicate through encrypted Nostr direct messages
- Keep responses concise and clear
...
Assembled document (revised, cleaner)
system:
# Simon — Didactyl Agent
You are Simon, a sovereign AI agent living on Nostr.
- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae`
- **NIP-05**: simon@nostr
> Not your keys, not your agent.
## Personality
You speak concisely and directly.
You favor technical precision.
You use dry humor sparingly.
## Rules
- Communicate through encrypted Nostr direct messages
- Keep responses concise and clear
- Be helpful and technically accurate
- If unsure, state uncertainty directly
- Use tools when a request requires taking action
- After a tool call, base your answer on the actual tool result
- Never claim a tool was run if no tool was executed
- Never reveal your private key (nsec)
- You may share your public key (npub) with anyone
---
## Chat
Respond helpfully to the admin.
Use tools when a request requires taking action.
user:
Hey Simon, who mentioned me on Nostr today?
This reads like a proper document. The runtime adds # Simon — Didactyl Agent
as the document title. Identity content flows as the opening section. Each
skill's # headings get bumped to ##. The --- separates skills.
Formatter Registry
The template engine uses formatters to convert tool JSON output to markdown for context injection. Here are the formatters needed:
| Template Variable | Tool Called | Formatter | Output |
|---|---|---|---|
{{agent_identity}} |
agent_identity |
format_agent_identity |
Prose with npub in backticks |
{{agent_profile}} |
nostr_agent_profile |
format_agent_profile |
Name extracted from kind 0 JSON |
{{admin_profile}} |
nostr_admin_profile |
format_admin_profile |
Name and about from kind 0 |
{{admin_notes}} |
nostr_admin_notes |
format_admin_notes |
Markdown list of recent notes |
{{admin_relays}} |
nostr_admin_relays |
format_admin_relays |
Markdown list of relay URLs |
{{dm_history}} |
built-in | format_dm_history |
Markdown conversation list |
{{triggering_event}} |
trigger_event |
format_trigger_event |
Code block with JSON |
Example formatter: agent_identity
Input (from tool):
{"pubkey":"abc123...","npub":"npub1kfc89..."}
Output (markdown):
- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae`
Example formatter: dm_history
Input (from DM history system):
[
{"role":"assistant","content":"Didactyl has started up...","created_at":1774263529},
{"role":"user","content":"Good morning","created_at":1774263548},
{"role":"assistant","content":"Good morning! How can I help?","created_at":1774263551}
]
Output (markdown):
## Conversation History
- **You**: Didactyl has started up and is online at 2026-03-23 09:22:48.
- **Admin**: Good morning
- **You**: Good morning! How can I help?
Example formatter: triggering_event
Input (from tool):
{"type":"cron","filter":"0 12 * * *","created_at":1742641200}
Output (markdown):
Triggering event:
```json
{"type":"cron","filter":"0 12 * * *","created_at":1742641200}
```
Heading Bump Implementation
Simple string operation on each line of skill content:
// For each line in skill content:
if (line starts with '#') {
output '#' + line; // prepend one more #
} else {
output line;
}
Layer 1 skills: bump once (# -> ##) Layer 2 skills: bump twice (# -> ###) because they are embedded inside a layer 1 skill that already got bumped once, plus one more for being layer 2.
Actually, layer 2 skills get bumped once during their own resolution, and then the containing layer 1 skill gets bumped once — so the layer 2 content effectively gets bumped twice. This happens naturally if we bump during template resolution.
Wait — that depends on ordering. If we:
- Resolve {{identity}} first (inserting identity content into personality)
- Then bump personality's headings
Then identity headings get bumped once (same as personality). That's correct because identity content is now PART of personality.
If we:
- Bump identity headings first
- Then resolve {{identity}} into personality
- Then bump personality's headings
Then identity headings get bumped twice. That's too much.
Correct order: resolve templates first, then bump headings.
This means layer 2 content gets the same heading level as the layer 1 skill it's embedded in. If the skill author wants layer 2 content at a deeper level, they wrap it in a heading in the layer 1 skill:
## Identity
{{identity}}
## Personality
...
This is the simplest and most predictable behavior.
Summary of Design Decisions
- Tools stay JSON — No duplication. One tool, one output format.
- Formatter registry — Template engine converts tool JSON to markdown
during
{{variable}}resolution. Small, focused functions. - Runtime owns h1 — Adds
# Agent Nameas document title. - Heading bump — All skill headings bumped one level (# -> ##).
- Resolve then bump — Template variables resolved first, then heading bump applied to the complete skill content.
- Skills separated by --- — Horizontal rules between layer 1 skills.
- Role markers parsed —
system:/user:at line start split content into API message roles. Unmarked content defaults to system. - Context log shows markdown — The assembled document is logged as readable markdown, not raw JSON.