Skip to content

Quarry API

The QuarryAPI is the core data interface for Stoneforge. It manages elements (tasks, documents, entities, etc.), dependencies, plans, workflows, channels, and sync operations. The Orchestrator API extends this with agent-specific capabilities.

Initialization

import { createQuarryAPI } from '@stoneforge/quarry';
import { createStorage, initializeSchema } from '@stoneforge/storage';
const storage = createStorage({ path: '.stoneforge/stoneforge.db' });
initializeSchema(storage);
const api = createQuarryAPI(storage);

CRUD operations

Create

const task = await api.create({
type: 'task',
createdBy: entityId,
title: 'Implement feature',
priority: 2,
taskType: 'feature',
});

Required fields for all elements: type, createdBy. Each element type has its own additional fields — see Core Types.

Read

const task = await api.get(taskId);
// With hydration (resolve document references)
const hydrated = await api.get(taskId, {
hydrate: { description: true, content: true, attachments: true },
});

Update

const updated = await api.update(taskId, {
status: 'in_progress',
assignee: entityId,
});

Delete

Soft-delete (tombstone). The element is marked as deleted but not removed.

await api.delete(taskId);

Query operations

List with filters

const tasks = await api.list({
type: 'task',
status: 'open', // Exact match
priority: 2,
tags: ['urgent'], // AND logic (all required)
tagsAny: ['frontend', 'backend'], // OR logic (any matches)
});
// Arrays for OR matching
const tasks = await api.list({
type: 'task',
status: ['open', 'in_progress'], // Either status
priority: [1, 2], // Either priority
});

Paginated list

const result = await api.listPaginated({
type: 'task',
status: 'open',
limit: 20,
offset: 0,
});
// result.items, result.total, result.hasMore

Document filtering

// By category
const specs = await api.list({ type: 'document', category: 'spec' });
// By status (default: 'active' only)
const archived = await api.list({ type: 'document', status: 'archived' });
// Paginated with both filters
const result = await api.listPaginated({
type: 'document',
category: 'prd',
status: 'active',
limit: 20,
});
const results = await api.search('keyword', {
type: ['task', 'document'], // Accepts ElementType or ElementType[]
});

Full-text document search (FTS5)

const results = await api.searchDocumentsFTS('search query', {
category: 'spec', // Optional category filter
status: 'active', // Optional (default: 'active')
hardCap: 50, // Max results before adaptive filtering
elbowSensitivity: 1.5, // Adaptive top-K sensitivity
minResults: 1, // Minimum results to return
});

Uses FTS5 with BM25 ranking, snippet generation, and adaptive elbow detection for top-K filtering.

const channels = await api.searchChannels('channel-name');

Task operations

Ready tasks

Tasks that are unblocked, open, not in draft plans, and not future-scheduled.

const ready = await api.ready();
const withEphemeral = await api.ready({ includeEphemeral: true });

ready() excludes:

  • Blocked tasks
  • Tasks in draft plans
  • Future-scheduled tasks (scheduledFor > now)
  • Ephemeral workflow tasks (unless includeEphemeral: true)

Backlog tasks

const backlog = await api.backlog();
const filtered = await api.backlog({ priority: 1 });

Blocked tasks

const blocked = await api.blocked();

Dependency operations

Add / remove

await api.addDependency({
blockedId: taskA,
blockerId: taskB,
type: 'blocks',
actor: actorId, // Optional, falls back to blocked element's createdBy
});
await api.removeDependency(blockedId, blockerId, 'blocks');

Query

// What this element depends on
const deps = await api.getDependencies(elementId, types?);
// What depends on this element
const dependents = await api.getDependents(elementId, types?);
// Full tree
const tree = await api.getDependencyTree(elementId);

Gate satisfaction

await api.satisfyGate(blockedId, blockerId, actor);
await api.recordApproval(blockedId, blockerId, approverId);
await api.removeApproval(blockedId, blockerId, approverId);

Plan operations

// Add/remove tasks
await api.addTaskToPlan(taskId, planId); // taskId first!
await api.removeTaskFromPlan(taskId, planId, actor?);
await api.createTaskInPlan(planId, { title: 'Task', priority: 2, createdBy: actorId });
// Query
const tasks = await api.getTasksInPlan(planId);
const progress = await api.getPlanProgress(planId);
// Bulk operations
await api.bulkClosePlanTasks(planId, { closeReason: 'Done' });
await api.bulkDeferPlanTasks(planId, { filter: { status: 'open' } });
await api.bulkReassignPlanTasks(planId, newAssigneeId);
await api.bulkTagPlanTasks(planId, { addTags: ['v2'], removeTags: ['v1'] });

Workflow operations

const tasks = await api.getTasksInWorkflow(workflowId);
const ready = await api.getReadyTasksInWorkflow(workflowId);
const ordered = await api.getOrderedTasksInWorkflow(workflowId); // Topological sort
const progress = await api.getWorkflowProgress(workflowId);
await api.deleteWorkflow(workflowId); // Hard delete

Garbage collection

await api.garbageCollectWorkflows({ maxAgeMs: 7 * 24 * 60 * 60 * 1000 });
await api.garbageCollectTasks({ maxAgeMs: 7 * 24 * 60 * 60 * 1000 });

Channel operations

// Find or create direct channel
const { channel, created } = await api.findOrCreateDirectChannel(entityA, entityB, actor);
// Membership
await api.addChannelMember(channelId, entityId);
await api.removeChannelMember(channelId, entityId);
await api.leaveChannel(channelId, actor);
// Merge channels
const result = await api.mergeChannels(sourceId, targetId, {
newName: 'merged-channel',
actor: actorEntityId,
});
// result.target, result.sourceArchived, result.messagesMoved

Entity operations

const entity = await api.lookupEntityByName('agent-name');
await api.setEntityManager(entityId, managerId, actor);
await api.clearEntityManager(entityId, actor);
const reports = await api.getDirectReports(managerId);
const chain = await api.getManagementChain(entityId);
const orgChart = await api.getOrgChart(rootEntityId?);

Send direct message

Convenience method — finds or creates a direct channel and sends a message in one call.

const result = await api.sendDirectMessage(senderEntityId, {
recipient: recipientEntityId,
contentRef: documentId, // Must create the Document first
attachments: [docId1], // Optional
tags: ['urgent'], // Optional
});
// result.channel, result.message, result.channelCreated

Document operations

Archive / unarchive

await api.archiveDocument(docId);
await api.unarchiveDocument(docId);
// Equivalent: api.update(docId, { status: 'archived' })

Embedding service

Register an embedding service for automatic semantic indexing:

import { EmbeddingService, LocalEmbeddingProvider } from '@stoneforge/quarry/services';
const provider = new LocalEmbeddingProvider('/path/to/model');
const embeddingService = new EmbeddingService(storage, { provider });
// Auto-embed on create/update, auto-remove on delete
api.registerEmbeddingService(embeddingService);

Reindex FTS

const result = api.reindexAllDocumentsFTS();
// result.indexed, result.errors

History & timeline

const events = await api.getEvents(elementId);
const allEvents = await api.listEvents({ eventType: 'created' });
const count = await api.countEvents({ elementId });
// Document versioning
const version = await api.getDocumentVersion(docId, versionNum);
const history = await api.getDocumentHistory(docId);
// Time travel
const snapshot = await api.reconstructAtTime(elementId, timestamp);
const timeline = await api.getElementTimeline(elementId);

Sync operations

Export

await api.export();
await api.export({ outputPath: './export.jsonl' });
await api.export({ includeDeleted: true });

ExportOptions

Parameter Type Default Description
format string jsonl Export format.
types ElementType[] Element types to export (default: all).
modifiedAfter Timestamp Only elements modified after this time.
includeDeleted boolean Include soft-deleted elements.
includeDependencies boolean Export dependencies.
includeEvents boolean Export events.
outputPath string Output file path (returns string if omitted).

Import

await api.import({ inputPath: './export.jsonl' });
await api.import({ data: jsonlString });
await api.import({ inputPath: './data.jsonl', dryRun: true });

ImportOptions

Parameter Type Default Description
inputPath string Input file path.
data string Raw JSONL data (alternative to inputPath).
conflictStrategy string error Conflict resolution: 'skip', 'overwrite', or 'error'.
validateFirst boolean Validate all data before importing.
dryRun boolean Validate without importing.

System

const stats = await api.stats();

Common patterns

Task with description

// 1. Create description document
const desc = await api.create({
type: 'document',
createdBy: actorId,
title: 'Task Description',
content: '# Requirements\n\n...',
contentType: 'markdown',
});
// 2. Create task referencing the description
const task = await api.create({
type: 'task',
createdBy: actorId,
title: 'Implement feature',
descriptionRef: desc.id,
priority: 2,
taskType: 'feature',
});

Check if a task is blocked

// Via API
const blockedTasks = await api.blocked();
const isBlocked = blockedTasks.some(t => t.id === taskId);
// Via BlockedCacheService (O(1) lookup)
import { createBlockedCacheService } from '@stoneforge/quarry';
const blockedCache = createBlockedCacheService(storage);
const isBlocked = blockedCache.isBlocked(taskId);

Multi-filter query

const tasks = await api.list({
type: 'task',
status: ['open', 'in_progress'],
priority: [1, 2],
assignee: agentId,
tags: ['urgent'],
});