Implement M3.5.7: Rate limiting + idempotency (20 tests)
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
# Guide: Writing & Uploading Skills/Memory for Temporal Workflows
|
||||
|
||||
Use the memory service to store context, tool patterns, and solutions that Temporal workflows can retrieve and use.
|
||||
|
||||
## Overview
|
||||
|
||||
**Three ways to get data into memory:**
|
||||
|
||||
1. **Ingest transcripts** (dialog with tool use + results) → system extracts skills
|
||||
2. **Direct skill upload** (manual structured skill)
|
||||
3. **Git corpus** (reference documentation, no extraction needed)
|
||||
|
||||
For Temporal workflows, option **1 (transcripts)** is most powerful: you capture a successful workflow execution, memory system learns the pattern, and future workflows query it.
|
||||
|
||||
---
|
||||
|
||||
## Format 1: Transcript-Based (Recommended)
|
||||
|
||||
Write a conversation showing a workflow using tools successfully. Memory extracts reusable skills.
|
||||
|
||||
### File Format
|
||||
|
||||
Create JSONL (one JSON object per line):
|
||||
|
||||
```jsonl
|
||||
{"role":"user","text":"Deploy service foo to prod","timestamp":"2025-01-15T10:00:00Z","source_position":0}
|
||||
{"role":"assistant","text":"I'll deploy foo using kubectl","timestamp":"2025-01-15T10:00:01Z","source_position":1}
|
||||
{"role":"tool_result","text":"kubectl apply -f foo.yaml\nDeployment foo created","timestamp":"2025-01-15T10:00:02Z","source_position":2}
|
||||
{"role":"assistant","text":"Deployment successful","timestamp":"2025-01-15T10:00:03Z","source_position":3}
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
- `role` — `user`, `assistant`, `tool_result`, `system`
|
||||
- `text` — actual message/command/result
|
||||
- `timestamp` — ISO8601 (e.g., `2025-01-15T10:00:00Z`)
|
||||
- `source_position` — line number in original source (for tracking)
|
||||
|
||||
### Upload via HTTP
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/memory/ingest \
|
||||
-H "apikey: YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"project": "temporal-workflows",
|
||||
"source": "transcript:slack/deployment-patterns",
|
||||
"ingest_id": "deploy-2025-01-15-abc123",
|
||||
"records": [
|
||||
{
|
||||
"role": "user",
|
||||
"text": "Deploy service foo to prod",
|
||||
"timestamp": "2025-01-15T10:00:00Z",
|
||||
"source_position": 0
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"text": "I'll deploy foo using kubectl apply",
|
||||
"timestamp": "2025-01-15T10:00:01Z",
|
||||
"source_position": 1
|
||||
},
|
||||
{
|
||||
"role": "tool_result",
|
||||
"text": "kubectl apply -f foo.yaml\nDeployment foo created",
|
||||
"timestamp": "2025-01-15T10:00:02Z",
|
||||
"source_position": 2
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"ingest_id": "deploy-2025-01-15-abc123",
|
||||
"status": "pending",
|
||||
"status_url": "/memory/ingest/deploy-2025-01-15-abc123"
|
||||
}
|
||||
```
|
||||
|
||||
Check status:
|
||||
```bash
|
||||
curl -H "apikey: YOUR_API_KEY" \
|
||||
http://localhost:8080/memory/ingest/deploy-2025-01-15-abc123
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Format 2: Structured Skill (Direct)
|
||||
|
||||
If you want to upload a pre-written skill without going through extraction:
|
||||
|
||||
### YAML Format (for manual storage)
|
||||
|
||||
Create `skills/temporal-patterns.yaml`:
|
||||
|
||||
```yaml
|
||||
name: "kubernetes_deploy_pattern"
|
||||
description: "Safe deployment pattern using kubectl apply with validation"
|
||||
when_to_use: "When deploying services to Kubernetes cluster"
|
||||
examples:
|
||||
- |
|
||||
kubectl apply -f service.yaml
|
||||
kubectl rollout status deployment/service -n default
|
||||
kubectl get pods -n default
|
||||
prerequisites:
|
||||
- "kubectl binary installed"
|
||||
- "kubeconfig configured"
|
||||
- "deployment manifest exists"
|
||||
steps:
|
||||
- "Validate manifest: kubectl apply -f service.yaml --dry-run=client"
|
||||
- "Apply: kubectl apply -f service.yaml"
|
||||
- "Monitor: kubectl rollout status deployment/service -n default"
|
||||
- "Verify: kubectl get pods, check for Ready status"
|
||||
precautions:
|
||||
- "Never use --force unless necessary"
|
||||
- "Always check diff before applying to prod"
|
||||
- "Rollback plan: kubectl rollout undo deployment/service"
|
||||
```
|
||||
|
||||
Then ingest as system memory:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/memory/ingest \
|
||||
-H "apikey: YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"project": "temporal-workflows",
|
||||
"source": "skill:manual/kubernetes",
|
||||
"ingest_id": "skill-k8s-deploy-001",
|
||||
"records": [
|
||||
{
|
||||
"role": "system",
|
||||
"text": "SKILL: kubernetes_deploy_pattern\n\nSafe deployment pattern using kubectl apply with validation\n\nWhen to use: When deploying services to Kubernetes cluster\n\nSteps:\n1. Validate manifest: kubectl apply -f service.yaml --dry-run=client\n2. Apply: kubectl apply -f service.yaml\n3. Monitor: kubectl rollout status deployment/service -n default\n4. Verify: kubectl get pods, check for Ready status\n\nPrecautions:\n- Never use --force unless necessary\n- Always check diff before applying to prod\n- Rollback plan: kubectl rollout undo deployment/service",
|
||||
"timestamp": "2025-01-15T10:00:00Z",
|
||||
"source_position": 0
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Format 3: Git Corpus (Reference Docs)
|
||||
|
||||
Documentation (never evidence, read-only for context).
|
||||
|
||||
Store in your obsidian-memory repo, then:
|
||||
|
||||
```bash
|
||||
mem ingest --source git:ssh://[email protected]:2222/rock/poimen-obesdient-memory.git \
|
||||
--project temporal-workflows
|
||||
```
|
||||
|
||||
(This will be auto-triggered by CI once M3.5.9 is done.)
|
||||
|
||||
---
|
||||
|
||||
## Querying Skills in Temporal Workflows
|
||||
|
||||
### Get all skills for a project
|
||||
|
||||
```bash
|
||||
curl -H "apikey: YOUR_API_KEY" \
|
||||
"http://localhost:8080/memory/skills?project=temporal-workflows"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"skills": [
|
||||
{
|
||||
"name": "kubernetes_deploy_pattern",
|
||||
"description": "Safe deployment pattern using kubectl apply with validation",
|
||||
"when_to_use": "When deploying services to Kubernetes cluster"
|
||||
},
|
||||
{
|
||||
"name": "postgres_backup_pattern",
|
||||
"description": "Automated backup with verification",
|
||||
"when_to_use": "Backup Postgres database before migrations"
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
```
|
||||
|
||||
### Get tool context (tool failure context + similar cases)
|
||||
|
||||
```bash
|
||||
curl -H "apikey: YOUR_API_KEY" \
|
||||
"http://localhost:8080/memory/context?tool=kubectl&error=connection+refused"
|
||||
```
|
||||
|
||||
Returns:
|
||||
- **Tier 1** — Exact match (same error + context)
|
||||
- **Tier 2** — Similar symptom (vector search)
|
||||
- **Tier 3** — Reference docs (R corpus)
|
||||
|
||||
*Note: This endpoint is M3.7.4 (in progress).*
|
||||
|
||||
---
|
||||
|
||||
## Example: Temporal Activity + Memory Query
|
||||
|
||||
```go
|
||||
// activity.go
|
||||
func QueryMemoryForPattern(ctx context.Context, toolName string, errorMsg string) (string, error) {
|
||||
resp, err := http.Get(fmt.Sprintf(
|
||||
"http://memory-service/memory/context?tool=%s&error=%s",
|
||||
url.QueryEscape(toolName),
|
||||
url.QueryEscape(errorMsg),
|
||||
))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
|
||||
// Use tier 1 (exact match) if available, else tier 2 (symptom), else tier 3 (docs)
|
||||
if tier1, ok := result["tier_1"].(string); ok && tier1 != "" {
|
||||
return tier1, nil
|
||||
}
|
||||
// ... same for tier 2, tier 3
|
||||
|
||||
return "", nil
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Source naming** — `source` field identifies where data came from:
|
||||
- `transcript:slack/topic`
|
||||
- `transcript:github/issue-123`
|
||||
- `skill:manual/pattern-name`
|
||||
- `git:ssh://git@.../repo.git`
|
||||
- `doc:obsidian-vault/path/to/note`
|
||||
|
||||
2. **Idempotency** — `ingest_id` must be stable (use SHA256 of content):
|
||||
```bash
|
||||
ingest_id=$(echo "temporal-deploy-pattern-2025-01-15" | sha256sum | cut -d' ' -f1)
|
||||
```
|
||||
|
||||
3. **Batch ingests** — upload multiple transcripts in one request to reduce overhead.
|
||||
|
||||
4. **Timestamps** — use workflow execution time, not current time. Helps memory system understand sequence.
|
||||
|
||||
5. **Project naming** — use consistent project keys (e.g., `temporal-workflows`, `agent-rust`, `poimen`).
|
||||
|
||||
---
|
||||
|
||||
## Ingesting from Temporal Directly
|
||||
|
||||
**Pseudo-code** (implement in your Temporal activity):
|
||||
|
||||
```go
|
||||
func IngestWorkflowToMemory(ctx context.Context, execution WorkflowExecution) error {
|
||||
records := []Record{}
|
||||
|
||||
// Walk through history and extract tool results
|
||||
for _, event := range execution.History.Events {
|
||||
if event.Type == "ActivityCompleted" {
|
||||
records = append(records, Record{
|
||||
Role: "tool_result",
|
||||
Text: event.Result,
|
||||
Timestamp: event.Timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// POST to /memory/ingest
|
||||
body := map[string]interface{}{
|
||||
"project": "temporal-workflows",
|
||||
"source": "temporal:workflow/" + execution.WorkflowID,
|
||||
"ingest_id": execution.RunID, // idempotent
|
||||
"records": records,
|
||||
}
|
||||
|
||||
resp, err := http.Post("http://memory-service/memory/ingest",
|
||||
"application/json",
|
||||
jsonBody(body),
|
||||
)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Then, in your workflow, query back:
|
||||
|
||||
```go
|
||||
func QueryMemoryInWorkflow(ctx context.Context, q string) ([]Result, error) {
|
||||
resp, _ := http.Get(fmt.Sprintf(
|
||||
"http://memory-service/memory/query?project=temporal-workflows&query=%s",
|
||||
url.QueryEscape(q),
|
||||
))
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|---|---|---|
|
||||
| `401 unauthorized` | Missing apikey header | Add `-H "apikey: YOUR_KEY"` |
|
||||
| `202` then status `pending` forever | Ingest worker not running | Check `mem serve` is running with DB connection |
|
||||
| Skills not appearing | M4.1 (extraction) not implemented yet | Use Format 2 (direct skill) for now |
|
||||
| Rate limited (429) | Hit limit for project | Check rate limit, wait or use different apikey |
|
||||
| Duplicate `ingest_id` | Same payload ingested twice | Intentional (idempotency); returns same job_id |
|
||||
|
||||
---
|
||||
|
||||
## Timeline
|
||||
|
||||
| Task | Status | Impact |
|
||||
|---|---|---|
|
||||
| M3.5.2 (ingest endpoint) | ✅ | Upload transcripts now |
|
||||
| M3.5.5 (skills endpoint) | ✅ | Query skills now |
|
||||
| M4.1 (skill extraction) | 🟡 | Automatic extraction in progress |
|
||||
| M3.7.4 (context endpoint) | ⬜ | Tier-1 lookup not yet available |
|
||||
| M3.7.8 (symptom projection) | ⬜ | Tier-2 vector lookup not yet available |
|
||||
|
||||
**Actionable now:** Formats 1 & 2, endpoints work. Extract by hand or via M4.1 when ready.
|
||||
Reference in New Issue
Block a user