Agent Reference
Structured reference for AI agents using primkit primitives. All commands, flags, JSON output schemas, decision trees, and error patterns.
For human-readable guides, see the README and knowledgeprim Guide. For configuration, see configuration.
Install
Install only the primitives you need. Each is a standalone binary.
From GitHub releases (pre-built):
# Detect platform
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m); [[ "$ARCH" == "x86_64" ]] && ARCH="amd64"
# Install all four (or pick the ones you need)
for bin in taskprim stateprim knowledgeprim queueprim; do
VERSION="0.5.1" # check https://github.com/propifly/primkit/releases for latest
curl -sL "https://github.com/propifly/primkit/releases/download/v${VERSION}/${bin}_${VERSION}_${OS}_${ARCH}.tar.gz" | tar xz
done
sudo mv taskprim stateprim knowledgeprim queueprim /usr/local/bin/
From source (requires Go 1.26+):
git clone https://github.com/propifly/primkit.git && cd primkit && make build
# Binaries: bin/taskprim, bin/stateprim, bin/knowledgeprim, bin/queueprim
Verify:
taskprim --help
stateprim --help
knowledgeprim --help
queueprim --help
No configuration required. Databases auto-create on first use at ~/<primitive>/default.db.
Global Flags (all primitives)
| Flag | Type | Default | Description |
|---|---|---|---|
--db | string | ~/<primitive>/default.db | Path to SQLite database |
--config | string | config.yaml | Path to config file |
--format | string | varies | Output format. knowledgeprim: text (default), json. taskprim, stateprim, queueprim: table (default), json, quiet. |
Always use --format json for programmatic consumption.
taskprim
Task management with lifecycle tracking. Tasks follow: open -> done | killed. Supports structural task-to-task dependencies with cycle detection and frontier queries.
Commands
| Command | Synopsis | Flags |
|---|---|---|
add | add <what> | --context — additional context or notes; --label (default: []) — labels (repeatable or comma-separated); --list (default: default) — list to add the task to; --parent — parent task ID for subtasks; --source (default: cli) — who created this task; --waiting-on — what this task is blocked on |
dep add | dep add <task-id> <depends-on-id> | — |
dep ls | dep ls <task-id> | — |
dep rm | dep rm <task-id> <depends-on-id> | — |
deps-of | deps-of <task-id> | — |
done | done <id> [id...] | — |
edit | edit <id> | --add-label (default: []) — add labels (repeatable); --context — update context notes; --del-label (default: []) — remove labels (repeatable); --list — move to a different list; --parent — set or clear parent task ID; --waiting-on — set or clear (empty string) waiting_on; --what — update the task description |
export | export | --list — export only tasks from this list; --state — export only tasks in this state |
frontier | frontier | --list — filter by list |
get | get <id> | — |
import | import | --file — path to JSON file (default: stdin) |
kill | kill <id> | --reason — why this task is being dropped (required) |
labels | labels | --list — only labels from tasks in this list |
labels clear | labels clear <label> | --list — only clear from tasks in this list |
list | list | --label (default: []) — filter by label (repeatable, AND logic); --list — filter by list; --seen-by — tasks seen by this agent (use with --since); --since — time window for --seen-by (e.g., 24h, 7d); --source — filter by source; --stale — tasks not updated within duration (e.g., 7d); --state — filter by state: open, done, killed; --unseen-by — tasks not seen by this agent; --waiting — only tasks with waiting_on set |
lists | lists | — |
restore | restore | — |
seen | seen <agent> [task_ids...] | --list — mark all open tasks in this list as seen |
stats | stats | — |
version | version | — |
JSON Schemas
Task object:
{
"id": "t_x7k2m9p4n1",
"list": "ops",
"what": "Deploy v2 to staging",
"source": "johanna",
"state": "open",
"waiting_on": "CI pipeline",
"parent_id": "t_abc123",
"context": "Blocked on flaky test fix",
"labels": ["deploy", "urgent"],
"created": "2026-03-04T10:00:00Z",
"updated": "2026-03-04T10:00:00Z",
"resolved_at": null,
"resolved_reason": null
}
**list --format json** returns [Task, ...]. **stats --format json** returns:
{
"total_open": 12,
"total_done": 45,
"total_killed": 3
}
DepEdge object (dep ls --format json, dep-edges API):
[
{
"task_id": "t_blockedTask",
"depends_on": "t_prerequisite"
}
]
**frontier --format json** returns [Task, ...] — open tasks with all dependencies resolved or no dependencies.
Idempotency
| Command | Idempotent | Notes |
|---|---|---|
add | No | Creates a new task every call. Deduplicate by checking list --format json first |
list, get, labels, lists, stats, frontier | Yes | Read-only |
done, kill | Yes | Calling on already-resolved task is a no-op |
edit | Yes | Same edit applied twice produces same result |
seen | Yes | Re-marking as seen updates the timestamp |
dep add | Yes | Adding an existing edge is a no-op (INSERT OR IGNORE) |
dep rm | Yes | Removing a non-existent edge is a no-op |
dep ls, deps-of | Yes | Read-only |
export | Yes | Read-only |
Decision Tree
- Create a task:
add "<what>" --list <list> --source <agent-name> - Check for existing work:
list --list <list> --state open --format json - Track what you've seen:
seen <agent-name>then laterlist --unseen-by <agent-name> - Mark complete:
done <id>(successful) orkill <id>(abandoned) - Subtasks:
add "<what>" --list <list> --parent <parent-id> - Add dependency (B depends on A):
dep add <B-id> <A-id> - Remove dependency:
dep rm <B-id> <A-id> - Check what a task depends on:
dep ls <id> --format json - Check what depends on a task:
deps-of <id> --format json - What can I work on next?:
frontier --format json(orfrontier --list <list>)
stateprim
Namespaced key-value state with three access patterns: key-value, dedup, and append.
Commands
| Command | Synopsis | Flags |
|---|---|---|
append | append <namespace> <json-value> | — |
delete | delete <namespace> <key> | — |
export | export | --namespace — export only this namespace |
get | get <namespace> <key> | — |
has | has <namespace> <key> | — |
import | import | --file — path to JSON file (default: stdin) |
namespaces | namespaces | — |
purge | purge <namespace> <duration> | — |
query | query <namespace> | --count — return count only; --prefix — filter by key prefix; --since — only records updated within duration (e.g., 24h, 7d) |
restore | restore | — |
set | set <namespace> <key> <json-value> | --immutable — mark the record as immutable |
set-if-new | set-if-new <namespace> <key> <json-value> | — |
stats | stats | — |
version | version | — |
JSON Schemas
Record object:
{
"namespace": "config",
"key": "app.theme",
"value": "dark",
"immutable": false,
"created_at": "2026-03-04T10:00:00Z",
"updated_at": "2026-03-04T10:00:00Z"
}
**has output (text):** yes or no. **has --format json:**
{
"exists": true
}
**stats --format json:**
{
"total_records": 156,
"total_namespaces": 8
}
Idempotency
| Command | Idempotent | Notes |
|---|---|---|
set | Yes | Upsert — same key+namespace overwrites |
get, has, query, namespaces, stats | Yes | Read-only |
set-if-new | Yes | No-op if key exists (returns existing record) |
append | No | Creates a new immutable record every call. Key is auto-generated |
delete | Yes | Deleting non-existent key is a no-op |
purge | No | Permanently removes records |
Decision Tree
- Store state:
set <ns> <key> '<json>' - Read state:
get <ns> <key> --format json - Check before acting (dedup):
has <ns> <key>— returnsyes/no - Create only if missing:
set-if-new <ns> <key> '<json>' - Log an event:
append <ns> '<json>'(immutable, timestamped) - Read recent events:
query <ns> --since 24h --format json
knowledgeprim
Knowledge graph with typed entities, weighted edges, and hybrid search.
Commands
| Command | Synopsis | Flags |
|---|---|---|
capture | capture | --body — entity body text; --force — bypass embedding model mismatch check; --no-auto-connect — skip auto-connect; --properties — JSON properties; --source (default: cli) — who captured this; --threshold (default: 0.35) — auto-connect cosine distance threshold; --title (required) — entity title; --type (required) — entity type (article, thought, concept, pattern, etc.); --url — source URL |
connect | connect <source-id> <target-id> | --context — edge context (why this connection exists); --relationship (required) — relationship type; --weight (default: 1) — edge weight |
delete | delete <id> | — |
disconnect | disconnect <source-id> <target-id> <relationship> | — |
discover | discover | --bridges — find cross-cluster connectors; --clusters — find densely connected groups; --orphans — find entities with no edges; --temporal — show type distribution over time; --weak-edges — find edges with no context |
edge-edit | edge-edit <source-id> <target-id> <relationship> | --context — edge context; --weight — edge weight |
edit | edit <id> | --body — new body; --properties — JSON properties; --title — new title |
export | export | --type — export only entities of this type |
get | get <id> | — |
import | import | — |
re-embed | re-embed | — |
related | related <id> | --depth (default: 1) — traversal depth (hops); --direction (default: both) — edge direction: outgoing, incoming, both; --min-weight — minimum edge weight; --relationship — filter by relationship type |
relationships | relationships | — |
restore | restore | — |
search | search <query> | --force — bypass embedding model mismatch check; --limit (default: 20) — max results; --mode (default: hybrid) — search mode: hybrid, fts, vector; --type — filter by entity type |
stats | stats | — |
strengthen | strengthen <source-id> <target-id> <relationship> | — |
strip-vectors | strip-vectors | --confirm — confirm destructive operation |
types | types | — |
version | version | — |
JSON Schemas
Entity object:
{
"id": "e_x7k2m9p4n1",
"type": "pattern",
"title": "Retry with exponential backoff",
"body": "Retry failed HTTP calls with 2^n backoff capped at 30s.",
"url": "https://example.com/patterns/retry",
"source": "coding-agent",
"properties": {"language": "go", "category": "resilience"},
"created_at": "2026-03-04T10:00:00Z",
"updated_at": "2026-03-04T10:00:00Z",
"edges": []
}
Edge object (inside entity or discover results):
{
"source_id": "e_x7k2m9p4n1",
"target_id": "e_a3b4c5d6e7",
"relationship": "extends",
"weight": 2.0,
"context": "Adds jitter to the basic exponential backoff pattern",
"created_at": "2026-03-04T10:00:00Z",
"updated_at": "2026-03-04T10:00:00Z"
}
**search --format json** returns:
[
{
"entity": { "id": "e_...", "type": "...", "title": "...", "..." : "..." },
"score": 0.8542
}
]
**related --format json** returns:
[
{
"entity": { "id": "e_...", "type": "...", "title": "...", "..." : "..." },
"relationship": "extends",
"direction": "outgoing",
"depth": 1,
"weight": 2.0
}
]
**discover --format json** returns:
{
"orphans": [{"id": "e_...", "type": "...", "title": "..."}],
"clusters": [{"entities": [...], "size": 7}],
"bridges": [{"entity": {...}, "edge_count": 12, "cluster_ids": [0, 2]}],
"temporal": [{"period": "2026-W09", "type": "pattern", "count": 5}],
"weak_edges": [{"source_id": "e_...", "target_id": "e_...", "relationship": "...", "weight": 1.0}]
}
**stats --format json:**
{
"entity_count": 234,
"edge_count": 567,
"vector_count": 234,
"orphan_count": 12,
"type_count": 6,
"db_size_bytes": 1048576,
"db_path": "/home/user/.knowledgeprim/default.db"
}
Idempotency
| Command | Idempotent | Notes |
|---|---|---|
capture | No | Creates a new entity every call. Search first to avoid duplicates |
search, get, related, discover, types, relationships, stats | Yes | Read-only |
connect | No | Fails if edge already exists (same source, target, relationship) |
strengthen | No | Additive — increments weight by 1.0 each call |
edge-edit | Yes | Same edit applied twice produces same result |
disconnect | Yes | Deleting non-existent edge is a no-op |
edit | Yes | Same edit applied twice produces same result |
delete | Yes | Deleting non-existent entity is a no-op |
re-embed | No | Re-generates all vectors with current provider. Expensive (API calls per entity) |
strip-vectors | Yes | Deleting already-empty vectors is a no-op |
Search Mode Decision Tree
Need to find something?
├── Know the exact term or identifier?
│ └── Use --mode fts
├── Looking for conceptually similar content?
│ ├── Embedding configured?
│ │ └── Use --mode vector
│ └── No embedding?
│ └── Use --mode fts (try synonyms in query)
└── General search (most cases)?
└── Use --mode hybrid (default — gracefully degrades to FTS-only without embedding)
Common Workflows
Capture and connect:
knowledgeprim capture --type pattern --title "..." --body "..." --source agent
# Parse the ID from the JSON output
knowledgeprim connect <new-id> <existing-id> --relationship extends \
--context "Why these connect"
Search before capture (avoid duplicates):
RESULTS=$(knowledgeprim search "retry backoff" --type pattern --format json)
# If empty array, safe to capture. If not, consider strengthening existing edges instead.
Periodic maintenance:
knowledgeprim discover --orphans --weak-edges --format json
# Process orphans: connect or delete
# Process weak edges: add context via edge-edit
queueprim
Persistent work queues with priority, retries, and atomic dequeue. Jobs follow: pending → claimed → done / failed / dead.
Commands
Queue names and payloads are positional arguments, not flags. Commands that act on a specific job take the job ID as a positional argument.
| Command | Synopsis | Flags |
|---|---|---|
complete | complete <id> | --output — JSON output payload from the worker |
dequeue | dequeue <queue> | --timeout (default: 30m) — visibility timeout for claimed job; --timeout-wait — max time to wait (e.g. 5m); 0 = wait forever; --type — only claim jobs of this type; --wait — block until a job appears; --worker — worker name for claimed_by tracking (default: hostname) |
enqueue | enqueue <queue> <json_payload> | --delay — delay before job is visible, e.g. 5m, 1h; --max-retries — max retries before dead-letter (default 0 = one-shot); --priority (default: normal) — priority: high, normal, or low; --type — job type category for workers (e.g. ssh_auth_fail) |
export | export | --queue — export only jobs in this queue (default: all) |
extend | extend <id> | --by (default: 30m) — extension duration, e.g. 30m, 1h |
fail | fail <id> | --dead — force to dead-letter regardless of retry count; --reason — human-readable failure reason |
get | get <id> | — |
import | import | — |
list | list | --older-than — only jobs created before now-duration, e.g. 1h; --queue — filter to this queue; --status — filter by status: pending, claimed, done, failed, dead; --type — filter by job type |
peek | peek <queue> | — |
purge | purge <queue> | --older-than — only purge jobs older than this duration, e.g. 7d, 24h; --status (required) — status to purge: done, dead, failed (required) |
queues | queues | — |
release | release <id> | — |
restore | restore | — |
stats | stats | — |
version | version | — |
JSON Schemas
Job object:
{
"id": "q_x7k2m9p4n1",
"queue": "infra/fixes",
"type": "ssh_auth_fail",
"priority": "normal",
"payload": {"host": "web-01", "issue": "disk_full"},
"status": "claimed",
"claimed_by": "johanna",
"claimed_at": "2026-03-05T10:00:00Z",
"visible_after": "2026-03-05T10:30:00Z",
"completed_at": null,
"output": null,
"failure_reason": null,
"attempt_count": 1,
"max_retries": 3,
"created_at": "2026-03-05T09:58:00Z",
"updated_at": "2026-03-05T10:00:00Z"
}
**queues output:**
[
{"queue": "infra/fixes", "pending": 3, "claimed": 1, "done": 12, "failed": 0, "dead": 1}
]
**stats output:**
{
"total_pending": 5,
"total_claimed": 2,
"total_done": 148,
"total_failed": 3,
"total_dead": 1
}
dequeue exits 1 with "queue is empty" on stderr when the queue is empty (without --wait). Use --wait to block and poll every 2s until a job appears; combine with --timeout-wait to cap the wait duration.
Idempotency
| Command | Idempotent | Notes |
|---|---|---|
enqueue | No | Creates a new job every call |
dequeue | No | Atomically claims and removes from pending pool |
peek, get, list, queues, stats | Yes | Read-only |
complete | No | Terminal — cannot re-complete |
fail | No | Transitions status; retries decrement |
release | Yes | Releasing an already-pending job is a no-op |
extend | No | Additive — each call pushes the timeout further |
purge | No | Permanently deletes matching jobs |
Decision Tree
- Producer enqueues work:
enqueue <queue> '<json_payload>' - Worker claims next job:
dequeue <queue> --worker <name> --format json→ parseid - Long-running job (heartbeat):
extend <id> --by 30mbefore timeout expires - Mark success:
complete <id>(optionally--output '<json>') - Mark failure (retriable):
fail <id> --reason "..."→ retries ifmax_retries > attempt_count - Mark failure (permanent):
fail <id> --dead - Inspect queue without consuming:
peek <queue> - Clean up old done jobs:
purge <queue> --status done --older-than 7d
Error Patterns
All primitives return non-zero exit codes on error. Error messages go to stderr.
| Error | Cause | Recovery |
|---|---|---|
"type is required" | Missing --type flag on capture | Add --type <type> |
"title is required" | Missing --title flag on capture | Add --title "<title>" |
"relationship is required" | Missing --relationship on connect | Add --relationship <rel> |
"self-edges are not allowed" | Source and target are the same entity | Use different entity IDs |
"vector search requires an embedding provider" | Used --mode vector without embedding config | Configure embedding in config.yaml or use --mode fts |
"embedding model mismatch" | Configured embedding provider differs from what's in the db | Use --mode fts, run re-embed, match config to db, or pass --force |
"entity not found" | Entity ID doesn't exist | Verify ID with search or types |
"database is locked" | Another process holds the SQLite lock | Retry after a short delay (SQLite busy timeout handles most cases) |
"cyclic dependency" | dep add would create a cycle in the dependency graph | Restructure dependencies to avoid the cycle |
"self dependency" | dep add with same ID for both args | Use two different task IDs |
"task is resolved" | dep add on a done or killed task | Only add dependencies to open tasks |
"dependency not found" | dep rm for an edge that doesn't exist | Verify with dep ls first |
"list is required" | taskprim add without --list | Add --list <list> |
"namespace is required" | stateprim command without namespace arg | Provide namespace as first positional arg |
"queue is required" | queueprim command without queue positional arg | Provide queue as first positional arg |
"payload is required" | queueprim enqueue without payload positional arg | Provide JSON payload as second positional arg |
"payload must be valid JSON" | queueprim payload is not valid JSON | Wrap in single quotes; ensure valid JSON |
"job not found" | Job ID doesn't exist | Verify ID with list --format json |
"invalid status transition" | Operation not valid for current job status (e.g., completing a done job) | Check job status with get <id> first |
"queue is empty" (CLI: dequeue exit 1, peek exit 0) | dequeue or peek on an empty queue | Normal — poll again or exit worker loop |
Environment Variables
| Variable | Primitive | Overrides |
|---|---|---|
TASKPRIM_DB | taskprim | storage.db path |
STATEPRIM_DB | stateprim | storage.db path |
KNOWLEDGEPRIM_DB | knowledgeprim | storage.db path |
QUEUEPRIM_DB | queueprim | storage.db path |
TASKPRIM_LIST | taskprim | Default list name for new tasks |
ID Formats
| Primitive | Prefix | Example | Generated by |
|---|---|---|---|
| taskprim | t_ | t_x7k2m9p4n1 | Store on add |
| knowledgeprim (entity) | e_ | e_a3b4c5d6e7 | Store on capture |
| queueprim | q_ | q_x7k2m9p4n1 | Store on enqueue |
stateprim uses user-provided namespace + key pairs, not generated IDs.
