Core
Planning + focus
useveyraMemory
Semantic context
useveyra/memoryVoice
Realtime sessions
useveyra/voiceSolana
Identity + proofs
useveyra/solanauseveyra
A memory-aware AI planning, focus, progress, and achievement SDK for TypeScript.
useveyra turns unstructured thoughts into bounded plans, retrieves relevant personal context, tracks focus sessions without background timers, summarizes actual outcomes, and supports optional voice and Solana integrations. Its dependency-injected architecture works offline, with OpenAI, or with application-owned providers and persistence adapters.
Contents
Why Veyra
Most productivity APIs model a task as a row in a database. Veyra models a larger loop: capture an ambiguous thought, recall useful preferences, build a realistic plan, execute it in focus sessions, record outcomes, and turn those outcomes into gentle summaries or optional rewards.
flowchart LR
A["Unstructured thought"] --> B["Validate input"]
B --> C["Retrieve relevant memories"]
C --> D["Planning provider"]
D --> E["Validated bounded plan"]
E --> F["Focus session"]
F --> G["Progress summary"]
G --> H["Quest and achievement evaluation"]
H -. "reusable learning" .-> C
The core productivity model remains offchain. Solana is restricted to wallet identity, payments, achievement identifiers, and public proof hashes.
Feature matrix
| Capability | Offline | OpenAI | Browser-safe | Persistent by default |
| ------------------------------------ | :--------------------: | :----------: | :------------------------: | :-------------------: |
| Deterministic planning | Yes | — | Yes | No |
| Structured AI planning | — | Yes | Backend only | No |
| Semantic memory retrieval | With a custom provider | Yes | Backend only | No |
| Focus sessions | Yes | Optional | Yes | No |
| Statistical summaries | Yes | Not required | Yes | No |
| Quests, XP, and streaks | Yes | Not required | Yes | No |
| Realtime voice session configuration | — | Yes | Secret consumed in browser | No |
| Solana unsigned transactions | Yes | — | Wallet signs externally | No |
In-memory stores are deliberately process-local. Production applications should inject encrypted persistent adapters.
Requirements and installation
npm i useveyra
-
Node.js 20 or newer
-
TypeScript is recommended for the complete API contract
-
A server-side OpenAI API key for OpenAI planning, embeddings, or voice configuration
-
An explicit OpenAI planning model; the SDK does not assume model availability
-
An application-selected RPC endpoint and mint for Solana payments
The package publishes ESM, CommonJS, TypeScript declarations, declaration maps, and JavaScript source maps.
Quick starts
OpenAI planning
import {
InMemoryMemoryStore,
InMemoryProgressStore,
OpenAIEmbeddingProvider,
OpenAIPlanningProvider,
Veyra,
} from "useveyra";
const apiKey = process.env.OPENAI_API_KEY;
const model = process.env.VEYRA_OPENAI_MODEL;
if (!apiKey || !model) {
throw new Error("Set OPENAI_API_KEY and VEYRA_OPENAI_MODEL");
}
const veyra = new Veyra({
planningProvider: new OpenAIPlanningProvider(apiKey, model),
embeddingProvider: new OpenAIEmbeddingProvider(
apiKey,
process.env.VEYRA_EMBEDDING_MODEL,
),
memoryStore: new InMemoryMemoryStore(),
progressStore: new InMemoryProgressStore(),
});
const plan = await veyra.plans.create({
userId: "user_123",
thought:
"I need to finish the landing page, answer emails, and prepare tomorrow's launch.",
availableMinutes: 120,
energy: "medium",
maximumTasks: 4,
timezone: "Asia/Makassar",
});
console.log(plan.summary, plan.tasks, plan.influencedByMemories);
Fully offline planning
import {
DeterministicPlanningProvider,
FixedClock,
InMemoryMemoryStore,
InMemoryProgressStore,
Veyra,
} from "useveyra";
const clock = new FixedClock("2026-01-01T09:00:00.000Z");
let sequence = 0;
const nextId = () => `demo_${++sequence}`;
const veyra = new Veyra({
planningProvider: new DeterministicPlanningProvider(clock, nextId),
memoryStore: new InMemoryMemoryStore(),
progressStore: new InMemoryProgressStore(),
clock,
idGenerator: nextId,
});
const plan = await veyra.plans.create({
userId: "offline_user",
thought: "Draft the launch post, review it, schedule it",
availableMinutes: 75,
});
Run the repository example without credentials or network access:
npm run example:offline
System architecture
The Veyra facade coordinates domain behavior. Providers own model calls, stores own persistence, and injected clocks/ID generators make time-dependent behavior reproducible.
flowchart TB
subgraph Application["Application boundary"]
API["Veyra facade"]
Browser["Browser client"]
Wallet["Wallet adapter"]
end
subgraph Core["Core SDK"]
Plans["Planning service"]
Memory["Memory and ranking"]
Sessions["Session state machine"]
Progress["Summaries and rewards"]
end
subgraph Adapters["Injected adapters"]
PlanningProvider["PlanningProvider"]
EmbeddingProvider["EmbeddingProvider"]
MemoryStore["MemoryStore"]
ProgressStore["ProgressStore"]
Clock["Clock and ID generator"]
end
subgraph Optional["Optional subpath exports"]
Voice["OpenAI Realtime"]
Solana["Solana identity, proof, payment"]
end
API --> Plans
API --> Memory
API --> Sessions
API --> Progress
Plans --> PlanningProvider
Memory --> EmbeddingProvider
Memory --> MemoryStore
Sessions --> ProgressStore
Sessions --> Clock
Browser --> Voice
Wallet --> Solana
Planning request sequence
sequenceDiagram
participant App
participant V as Veyra
participant E as EmbeddingProvider
participant M as MemoryStore
participant P as PlanningProvider
participant S as ProgressStore
App->>V: plans.create(input, options)
V->>V: Zod validation
opt Embeddings configured
V->>E: embed(thought)
E-->>V: query vector
V->>M: list(userId)
M-->>V: candidate memories
V->>V: similarity + importance + recency + novelty
end
V->>P: createPlan(input + memory content)
P-->>V: structured Plan
V->>V: validate time bound
V->>S: savePlan(plan)
V-->>App: typed Plan
Configuration
Veyra accepts explicit dependencies:
| Property | Required | Purpose |
| ------------------- | :------: | -------------------------------------------- |
| planningProvider | Yes | Produces typed plans |
| memoryStore | Yes | Stores and retrieves personal memories |
| progressStore | Yes | Stores plans and focus sessions |
| embeddingProvider | No | Enables semantic memory ranking |
| clock | No | Controls time; defaults to SystemClock |
| idGenerator | No | Creates entity identifiers |
| xpPerTask | No | Configures non-negative XP per verified task |
Supported environment variables are documented in .env.example:
OPENAI_API_KEY=
VEYRA_OPENAI_MODEL=
VEYRA_EMBEDDING_MODEL=text-embedding-3-small
VEYRA_OPENAI_TIMEOUT_MS=30000
VEYRA_REALTIME_MODEL=
SOLANA_RPC_URL=https://api.devnet.solana.com
SOLANA_CLUSTER=devnet
Environment variables are application configuration; the SDK constructors receive explicit values and do not silently read process state.
Plans
Create a plan with veyra.plans.create(input, options?).
const plan = await veyra.plans.create(
{
userId: "user_123",
thought: "Review analytics and write the weekly update",
availableMinutes: 50,
energy: "low",
maximumTasks: 3,
currentTime: new Date().toISOString(),
timezone: "Europe/Amsterdam",
},
{ timeoutMs: 30_000, signal: abortController.signal },
);
Plan.tasks contain an ID, title, optional description, duration, energy, priority, reason, and lifecycle status. Veyra rejects a provider result whose total duration exceeds availableMinutes. influencedByMemories contains usable memory content, not vector values or private ranking metadata.
Memory
Save, search, update, and forget
const preference = await veyra.memory.remember({
userId: "user_123",
kind: "preference",
content: "I prefer writing work before meetings.",
importance: 0.8,
});
const matches = await veyra.memory.search({
userId: "user_123",
query: "When should I schedule writing?",
limit: 5,
kinds: ["preference", "habit"],
});
await veyra.memory.update({ id: preference.id, importance: 0.9 });
await veyra.memory.forget({ id: preference.id });
Memory kinds are goal, habit, preference, blocker, and reflection. Importance is normalized to 0..1.
Ranking model
When embeddings are configured, candidate memories are ranked locally:
score = similarity × 0.55
+ importance × 0.25
+ recency × 0.15
+ novelty × 0.05
| Component | Weight | Interpretation |
| ---------- | -----: | ------------------------------------------------- |
| Similarity | 55% | Cosine relevance between query and memory vectors |
| Importance | 25% | Application/user-assigned long-term value |
| Recency | 15% | Preference for recently updated information |
| Novelty | 5% | Mild preference for memories not used recently |
All components and the final score are bounded to 0..1.
Extraction modes
const result = await veyra.memory.extract({
userId: "user_123",
text: "I prefer quiet rooms. The weather is nice today.",
confirmationMode: "suggest",
});
-
automatic: persist reusable candidates immediately. -
suggest: return candidates without persistence. -
explicit: return candidates for an application-controlled confirmation flow.
Focus sessions
stateDiagram-v2
[*] --> Active: start
Active --> Paused: pause
Paused --> Active: resume
Active --> Completed: complete
Paused --> Completed: complete
Active --> Cancelled: cancel
Paused --> Cancelled: cancel
Completed --> [*]
Cancelled --> [*]
const session = await veyra.sessions.start({
userId: "user_123",
planId: plan.id,
taskId: plan.tasks[0]?.id,
plannedMinutes: 25,
});
await veyra.sessions.checkIn({
sessionId: session.id,
note: "First draft complete",
progress: 0.6,
interrupted: false,
});
await veyra.sessions.pause(session.id);
await veyra.sessions.resume(session.id);
const completed = await veyra.sessions.complete({ sessionId: session.id });
No permanent timer runs. Active elapsed time is derived from stored timestamps and the injected clock. Invalid transitions throw SessionStateError.
Summaries
const daily = await veyra.summaries.daily({
userId: "user_123",
date: "2026-08-04",
timezone: "Asia/Makassar",
});
const weekly = await veyra.summaries.weekly({
userId: "user_123",
start: "2026-08-03T00:00:00.000Z",
timezone: "Asia/Makassar",
});
Summaries report period boundaries, completed task/session counts, completed minutes, highlights, patterns, gentle suggestions, and a narrative based on recorded outcomes.
Quests, XP, and streaks
const quest = await veyra.quests.fromPlan({ planId: plan.id });
await veyra.quests.completeTask({
questId: quest.id,
taskId: plan.tasks[0]!.id,
verified: true,
idempotencyKey: `completion:${quest.id}:${plan.tasks[0]!.id}`,
});
const progress = veyra.quests.getProgress({ questId: quest.id });
const streak = await veyra.streaks.get({
userId: "user_123",
timezone: "Asia/Makassar",
});
XP is awarded only for verified task completion. Reusing an idempotency key or rewarding the same task twice raises DuplicateRewardError. Streak calendar dates are calculated in the supplied IANA timezone.
Achievements
Built-ins include First Focus, Deep Work, Seven Day Rhythm, Quest Complete, and Consistent Explorer.
const eligible = await veyra.achievements.evaluate({ userId: "user_123" });
const unlocked = await veyra.achievements.unlock({
achievementId: eligible[0]!.id,
});
const proof = await veyra.achievements.createProof({
achievementId: unlocked.id,
wallet: "A_VALID_SOLANA_WALLET_ADDRESS",
evidenceHash: "64_HEXADECIMAL_CHARACTERS",
});
Evaluation is deterministic and separate from blockchain transaction construction.
Voice
Voice is isolated behind useveyra/voice. The standard OpenAI key remains on a trusted server.
sequenceDiagram
participant Browser
participant Backend
participant OpenAI
Browser->>Backend: Request voice session
Backend->>OpenAI: Create ephemeral client secret
OpenAI-->>Backend: Short-lived secret
Backend-->>Browser: VoiceSessionConfig
Browser->>OpenAI: Connect with ephemeral secret
Server:
import { OpenAIVoiceProvider } from "useveyra/voice";
const voice = new OpenAIVoiceProvider(
process.env.OPENAI_API_KEY!,
process.env.VEYRA_REALTIME_MODEL,
);
const config = await voice.createSessionConfig({
instructions: "Be concise, calm, and task-focused.",
});
Browser-safe consumption:
import { consumeVoiceSessionConfig } from "useveyra/voice";
const config = consumeVoiceSessionConfig(await response.json());
Voice events cover transcript deltas, assistant audio, interruption, error, and session completion.
Solana
Wallet identity
import { InMemoryNonceStore } from "useveyra";
import { SolanaIdentityService } from "useveyra/solana";
const identity = new SolanaIdentityService(new InMemoryNonceStore());
const challenge = await identity.createChallenge({
domain: "app.example.com",
wallet: walletAddress,
expiresInSeconds: 300,
});
const result = await identity.verifyChallenge({
message: challenge.message,
wallet: walletAddress,
nonce: challenge.nonce,
signature: base58Signature,
});
Challenges bind domain, wallet, nonce, issued time, and expiration. Nonces are single-use and signatures are Ed25519-verified. The SDK never asks for a private key.
Achievement proof
import { SolanaAchievementService } from "useveyra/solana";
const service = new SolanaAchievementService();
const payload = service.createProofPayload({
wallet: walletAddress,
achievementId: "first-focus",
earnedAt: Math.floor(Date.now() / 1000),
version: 1,
evidenceHash: sha256Hex,
});
const transaction = service.buildProofTransaction({
proof: payload,
programId,
});
The returned transaction is unsigned and unbroadcast. Private task names, conversation text, habits, blockers, and memory content are never fields in AchievementProof.
USDC transfer
import { SolanaPaymentService } from "useveyra/solana";
const payments = new SolanaPaymentService();
const transaction = await payments.buildUsdcTransfer({
cluster: "devnet",
rpcUrl: "https://api.devnet.solana.com",
mint: configuredMint,
sender: senderWallet,
recipient: recipientWallet,
amount: "12.50",
decimals: 6,
});
Currency arrives as a decimal string and is converted to integer base units with BigInt; floating-point currency arithmetic is not used. Applications must select the correct mint for their cluster.
Custom adapters
import type {
Plan,
PlanningProvider,
PlanningProviderInput,
RequestOptions,
} from "useveyra";
class RulesPlanningProvider implements PlanningProvider {
async createPlan(
input: PlanningProviderInput,
_options?: RequestOptions,
): Promise<Plan> {
const createdAt = input.currentTime ?? new Date().toISOString();
return {
id: crypto.randomUUID(),
userId: input.userId,
summary: "One deliberately small next step.",
tasks: [
{
id: crypto.randomUUID(),
title: input.thought,
durationMinutes: Math.min(input.availableMinutes ?? 25, 25),
energy: input.energy ?? "medium",
priority: "medium",
reason: "Keep the first action concrete and bounded.",
status: "pending",
},
],
totalDurationMinutes: Math.min(input.availableMinutes ?? 25, 25),
checkInAfterMinutes: 15,
createdAt,
};
}
}
Custom MemoryStore and ProgressStore implementations can use PostgreSQL, DynamoDB, SQLite, or another application-controlled system without modifying Veyra.
Errors and request control
All SDK errors extend VeyraError and expose code, message, retryable, optional cause, and optional safe metadata.
| Error | Typical meaning |
| ------------------------ | --------------------------------------------- |
| ConfigurationError | Missing or invalid SDK/provider configuration |
| ValidationError | Invalid request or invalid provider result |
| AuthenticationError | Provider or wallet authentication failed |
| ProviderError | External provider failed |
| RateLimitError | Retryable provider throttling |
| TimeoutError | Request timed out or was aborted |
| MemoryStoreError | Memory persistence failed |
| ProgressStoreError | Progress persistence failed |
| SessionStateError | Invalid focus-session transition |
| DuplicateRewardError | Duplicate XP award attempt |
| SolanaTransactionError | Solana validation or construction failed |
| VoiceSessionError | Ephemeral voice configuration failed |
import { VeyraError } from "useveyra";
const controller = new AbortController();
try {
const plan = await veyra.plans.create(input, {
signal: controller.signal,
timeoutMs: 30_000,
idempotencyKey: "plan:user_123:2026-08-04",
});
} catch (error) {
if (error instanceof VeyraError) {
console.error({ code: error.code, retryable: error.retryable });
}
}
Retryable provider errors use bounded exponential backoff with jitter. Validation and authentication failures are not retried. Provider response bodies, tokens, and private memories are excluded from public error messages.
Privacy and threat boundaries
flowchart LR
Private["Private offchain data\nTasks, memories, habits, blockers"]
Backend["Trusted application backend"]
OpenAI["Configured AI provider"]
Public["Public Solana data\nWallet, achievement ID, time, version, hash"]
Browser["Browser"]
Private --> Backend
Backend -->|"selected planning context"| OpenAI
Backend -->|"ephemeral secret only"| Browser
Backend -->|"unsigned proof payload"| Public
Private -. "never serialized" .-> Public
-
Keep standard OpenAI keys on trusted servers.
-
Browser applications should call their own backend.
-
Never pass wallet private keys or seed phrases into Veyra.
-
Veyra builds unsigned Solana transactions; the wallet retains approval control.
-
Productivity data remains offchain.
-
Achievement proofs contain identifiers, timestamps, versions, wallet addresses, and hashes only.
-
Deleting an in-memory memory removes the complete record, including its embedding.
-
Avoid logging authentication tokens, provider keys, voice secrets, or memory content.
-
Encrypt production stores at rest and apply per-user authorization in the adapter layer.
Package exports
| Import | Contents | Intended environment |
| ----------------- | ----------------------------------------------------------- | --------------------------------------------------------------------- |
| useveyra | Core client, types, stores, utilities, providers, errors | Node.js/server; deterministic pieces also work in compatible runtimes |
| useveyra/server | Veyra and OpenAI providers | Trusted server |
| useveyra/memory | Memory store, scoring, and memory contracts | Server or application adapter layer |
| useveyra/voice | Realtime provider, voice contracts, browser config consumer | Server and browser split |
| useveyra/solana | Identity, achievement proof, and payment services | Server or wallet-enabled application |
Every export provides import, require, and declaration targets. The package declares sideEffects: false for tree shaking.
Development and publishing
npm install
npm run format:check
npm run lint
npm run typecheck
npm run test
npm run test:coverage
npm run build
npm run example:offline
npm pack --dry-run
Additional commands:
| Command | Purpose |
| ------------------------ | -------------------------------- |
| npm run dev | Watch the tsup build |
| npm run test:watch | Run Vitest interactively |
| npm run format | Apply Prettier formatting |
| npm run clean | Remove build and coverage output |
| npm run prepublishOnly | Run the configured release gate |
Before publishing, inspect the dry-run tarball, confirm no .env file or secret is present, and verify both ESM and CommonJS consumers.
npm login
npm publish --access public
Versions follow semantic versioning: patches fix compatible behavior, minors add compatible APIs, and majors may contain breaking contract changes.
License
MIT. See LICENSE.