216 lines
6.9 KiB
Markdown
216 lines
6.9 KiB
Markdown
# Agent Tasks: Short-Term Memory via Context-Injected Task List
|
|
|
|
## Summary
|
|
|
|
Add a **tasks** system that serves as the agent's short-term working memory. The agent can break down goals into steps, track progress, and see its current task list in every prompt context. Tasks are file-backed (not stored on Nostr) and managed via a dedicated `task_manage` tool.
|
|
|
|
## How It Works
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[User sends message] --> B[Context builder runs]
|
|
B --> C[Template resolver hits tasks_content variable]
|
|
C --> D[Read tasks.json from disk]
|
|
D --> E{Tasks exist?}
|
|
E -->|Yes| F[Format tasks as readable text]
|
|
E -->|No| G[Return empty string - section skipped]
|
|
F --> H[Inject as system message in prompt]
|
|
G --> H
|
|
H --> I[LLM sees current tasks in context]
|
|
I --> J{LLM decides to update tasks?}
|
|
J -->|Yes| K[LLM calls task_manage tool]
|
|
K --> L[Tool updates tasks.json on disk]
|
|
L --> M[Tool result returned to LLM]
|
|
J -->|No| N[LLM responds normally]
|
|
```
|
|
|
|
## Design
|
|
|
|
### Storage: `tasks.json`
|
|
|
|
A simple JSON file in the agent's working directory. Structure:
|
|
|
|
```json
|
|
{
|
|
"tasks": [
|
|
{
|
|
"id": 1,
|
|
"text": "Query admin relay list to find active relays",
|
|
"status": "done",
|
|
"created_at": 1709535600,
|
|
"updated_at": 1709535660
|
|
},
|
|
{
|
|
"id": 2,
|
|
"text": "Draft long-form article about Nostr relay setup",
|
|
"status": "active",
|
|
"created_at": 1709535600,
|
|
"updated_at": 1709535600
|
|
},
|
|
{
|
|
"id": 3,
|
|
"text": "Publish article as kind 30023",
|
|
"status": "pending",
|
|
"created_at": 1709535600,
|
|
"updated_at": 1709535600
|
|
}
|
|
],
|
|
"next_id": 4
|
|
}
|
|
```
|
|
|
|
Task statuses: `pending`, `active`, `done`
|
|
|
|
### Tool: `task_manage`
|
|
|
|
A single tool with an `action` parameter that covers all operations:
|
|
|
|
| Action | Parameters | Description |
|
|
|--------|-----------|-------------|
|
|
| `list` | *(none)* | Return all tasks with status |
|
|
| `add` | `text`, optional `status` | Add a new task, default status `pending` |
|
|
| `update` | `id`, optional `text`, optional `status` | Update text and/or status of a task |
|
|
| `remove` | `id` | Remove a task by ID |
|
|
| `clear` | optional `status` | Remove all tasks, or all with a given status |
|
|
| `replace` | `tasks` (array of text strings) | Replace entire task list with new items |
|
|
|
|
The `replace` action is important — it lets the LLM rewrite the whole plan in one call rather than doing add/remove/update one at a time. This is the most common pattern: the agent works out a plan and writes all steps at once.
|
|
|
|
**Tool schema:**
|
|
|
|
```json
|
|
{
|
|
"name": "task_manage",
|
|
"description": "Manage the agent task list - short-term working memory for tracking steps in a plan. Tasks appear in your context on every message.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"action": {
|
|
"type": "string",
|
|
"enum": ["list", "add", "update", "remove", "clear", "replace"]
|
|
},
|
|
"text": { "type": "string" },
|
|
"id": { "type": "integer" },
|
|
"status": { "type": "string", "enum": ["pending", "active", "done"] },
|
|
"tasks": {
|
|
"type": "array",
|
|
"items": { "type": "string" }
|
|
}
|
|
},
|
|
"required": ["action"]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Context Section: `agent_tasks`
|
|
|
|
New section in the context template, placed after `adopted_skills` and before `dm_history`:
|
|
|
|
```yaml
|
|
- section: agent_tasks
|
|
role: system
|
|
skip_if_empty: true
|
|
content: |
|
|
{{tasks_content}}
|
|
```
|
|
|
|
### Template Variable: `{{tasks_content}}`
|
|
|
|
New resolver in `agent_template_resolve_var()` that:
|
|
|
|
1. Reads `tasks.json` from the working directory
|
|
2. Parses the JSON
|
|
3. Formats active/pending tasks as readable text
|
|
4. Returns empty string if no tasks exist (section gets skipped via `skip_if_empty`)
|
|
|
|
**Rendered format in context:**
|
|
|
|
```
|
|
### Current Tasks
|
|
|
|
Your active task list - short-term working memory for tracking plan steps.
|
|
|
|
- [x] 1. Query admin relay list to find active relays
|
|
- [-] 2. Draft long-form article about Nostr relay setup
|
|
- [ ] 3. Publish article as kind 30023
|
|
```
|
|
|
|
Legend: `[x]` = done, `[-]` = active, `[ ]` = pending
|
|
|
|
Done tasks are included so the agent has continuity about what it already accomplished, but they could be pruned after a configurable count or age to save tokens.
|
|
|
|
### System Prompt Addition
|
|
|
|
Add to the agent's behavioral rules in the soul/system prompt:
|
|
|
|
```
|
|
### Task Management
|
|
- You have a task list that serves as your short-term working memory.
|
|
- When working on multi-step goals, use task_manage to track your plan.
|
|
- Update task status as you complete steps.
|
|
- Your current tasks appear in your context automatically.
|
|
```
|
|
|
|
## Implementation Steps
|
|
|
|
### 1. Add `task_manage` tool implementation in `tools.c`
|
|
|
|
- New `execute_task_manage()` function
|
|
- Reads/writes `tasks.json` in the working directory (uses `build_tool_path` for sandboxing)
|
|
- Handles all 6 actions: list, add, update, remove, clear, replace
|
|
- Returns JSON result with success/failure and current task list
|
|
|
|
### 2. Register `task_manage` tool schema in `tools_build_openai_schema_json()`
|
|
|
|
- Add tool definition (t35 or next available) with the schema above
|
|
|
|
### 3. Wire `task_manage` into `tools_execute()` dispatch
|
|
|
|
- Add `strcmp(tool_name, "task_manage")` branch calling `execute_task_manage()`
|
|
|
|
### 4. Add `{{tasks_content}}` template variable resolver in `agent.c`
|
|
|
|
- New `build_tasks_content_string()` function
|
|
- Reads `tasks.json`, formats as markdown checklist
|
|
- Add to `agent_template_resolve_var()` for var name `tasks_content`
|
|
|
|
### 5. Add `agent_tasks` section to context template
|
|
|
|
- Add the new section in `context_template.md`
|
|
- Place after `adopted_skills`, before `dm_history`
|
|
- Use `skip_if_empty: true` so it costs zero tokens when no tasks exist
|
|
|
|
### 6. Add section detection for context logging
|
|
|
|
- Add `agent_tasks` detection in `detect_context_section()` in `agent.c`
|
|
|
|
### 7. Add task management guidance to system prompt
|
|
|
|
- Brief behavioral instruction so the agent knows when/how to use the task list
|
|
|
|
## Token Budget Considerations
|
|
|
|
- Empty task list: **0 tokens** (skipped via `skip_if_empty`)
|
|
- Typical 5-task plan: **~80-120 tokens**
|
|
- Maximum reasonable list of 15 tasks: **~250-350 tokens**
|
|
- Consider pruning done tasks older than N turns or keeping only the last M done tasks
|
|
|
|
## Future: User-Facing To-Do List (Nostr)
|
|
|
|
This is explicitly **not** the user-facing to-do list. That future feature would:
|
|
- Store items as Nostr events (likely a NIP-51 style list or custom kind)
|
|
- Be visible to the user via Nostr clients
|
|
- Have its own separate tool (`todo_manage` or similar)
|
|
- Potentially reference agent tasks that graduate to user-visible items
|
|
|
|
The agent tasks system is purely internal working memory.
|
|
|
|
## Files Modified
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `src/tools.c` | Add `execute_task_manage()`, tool schema, dispatch entry |
|
|
| `src/agent.c` | Add `build_tasks_content_string()`, resolver entry, section detection |
|
|
| `context_template.md` | Add `agent_tasks` section |
|
|
| Soul/system prompt (kind 31120) | Add task management behavioral guidance |
|