Skip to content

Architecture

┌─────────────────────────┐
│ CLI / Telegram / MCP │
│ (src/index.ts) │
└────────────┬─────────────┘
┌────────────▼─────────────┐
│ Engine (singleton) │
│ (src/core/engine.ts) │
│ KG, Memory, DataStore, │
│ Secrets, Config, Tools, │
│ WorkerLoop, Notifier │
└──┬──────┬──────┬──────┬──┘
│ │ │ │
┌───────▼──────▼──────▼──────▼────┐
│ Session (per-conversation) │
│ (src/core/session.ts) │
│ Agent, messages, mode, │
│ callbacks, run tracking │
└──────────────┬───────────────────┘
┌────────▼────────┐
│ StreamProcessor │
│ (stream) │
└────────┬────────┘
┌───────────▼───────────┐
│ Anthropic SDK │
│ (beta messages API) │
└───────────────────────┘

Single source of truth for all types. Contains:

  • ModelTier, MODEL_MAP, CONTEXT_WINDOW — model tier definitions
  • ThinkingMode, EffortLevel — reasoning / accuracy configuration
  • ToolEntry, ToolHandler — tool contract
  • StreamEvent, StreamHandler — event union (includes trigger, cost_warning, continuation, advice)
  • IAgent, IMemory, IWorkerPool — core interfaces
  • AgentConfig, NodynConfig, NodynUserConfig, MCPServer — configuration
  • ChangesetEntry, ChangesetDiff, ChangesetResult, ChangesetManagerLike — changeset review types
  • SpawnSpec, BatchRequest, BatchResult — operation types (SpawnSpec includes role, context, isolated_memory)
  • Role, ToolScopeConfig — role-based agent configuration
  • TriggerConfig, ITrigger — triggers
  • NODYN_BETAS — beta header array
  • ALL_NAMESPACES — canonical list of all MemoryNamespace values (shared by knowledge system, knowledge-gc, knowledge tools)
  • TaskRecord, TaskStatus, TaskPriority — task management types. Task types: manual, scheduled, watch, pipeline
  • InlinePipelineStep includes role field — connects roles to DAG workflow steps
  • IsolationConfig, IsolationLevel — context isolation configuration (used by Pro tenant system)
  • MODEL_TIER_SET, EFFORT_LEVEL_SET, AUTONOMY_LEVEL_SET, SCOPE_TYPE_SET, OUTPUT_FORMAT_SET — centralized validation sets (never redeclare locally)

Runtime validation schemas for JSON-serializable config types. Used where untrusted JSON is parsed:

  • RoleSchema — validates role JSON files (used by parseRoleConfig())
  • NodynUserConfigSchema — validates config.json files (used by readConfigFile())
  • TenantConfig, TenantStatus — tenant lifecycle and budget types (used by Pro tenant system)
ModuleClassPurpose
agent.tsAgentAgentic loop with streaming, tool dispatch, permission checks, retry with backoff, knowledge context support (setKnowledgeContext())
stream.tsStreamProcessorAssembles stream deltas into content blocks, emits StreamEvents
memory.tsMemoryContext-scoped local file storage in ~/.nodyn/memory/<contextId>/ with global fallback, hasContent(), publishes to nodyn:memory:store. Unified scope-keyed cache (${type}:${id}:${ns}) covers global, project, and user scopes
engine.tsEngineShared singleton per process. Owns KG, Memory, DataStore, Secrets, Config, ToolRegistry, WorkerLoop, NotificationRouter. init() once, then createSession() for per-conversation state. NodynHooks.onAfterRun receives RunContext (includes runId, contextId, modelTier, durationMs, source, tenantId?). Hook errors logged to costWarning debug channel
session.tsSessionPer-conversation state created via engine.createSession(). Implements ModeOrchestrator. Owns Agent, messages, mode, callbacks, run tracking. Entry point for run(), batch(), shutdown(). Per-session config isolation: setModel(), setEffort(), setThinking() mutate session-local fields, not engine.config. SessionOptions: model, effort, thinking, autonomy, briefing, systemPromptSuffix
session-store.tsSessionStoreMulti-turn MCP session management
batch-index.tsBatchIndexPersists batch metadata to ~/.nodyn/batch-index.json
utils.tsShared utilities: sleep(), getErrorMessage(), sha256Short() (used by prompt-hash, run-history, project)
observability.tsnode:diagnostics_channel + node:perf_hooks instrumentation (includes nodyn:memory:store channel)
config.ts3-tier config merge (env > project > user), PROJECT_SAFE_KEYS allowlist, getNodynDir() canonical ~/.nodyn path, parse error warning to stderr
run-history.tsRunHistorySQLite via better-sqlite3 (WAL mode, 19 migrations) at ~/.nodyn/history.db. Delegates analytics to run-history-analytics.ts (9 query functions) and domain persistence to run-history-persistence.ts (44 functions). Tables: runs, tool_calls, spawns, prompt_snapshots, pre_approval, pipelines, scopes, tasks, security_events, processes
process-capture.tsExtracts ProcessRecord from run_history tool calls. Haiku call for step naming + parameter identification. Sanitizes secrets, filters internal tools
pricing.tsShared pricing table with calculateCost(), cache token support, optional JSON override
project.tsdetectProjectRoot(), generateBriefing(), buildFileManifest() + diffManifest()
prompt-hash.tsSHA-256 prompt versioning via sha256Short() from utils
embedding.tsEmbeddingProviderInterface with OnnxProvider (model registry: multilingual-e5-small default 384d, all-minilm-l6-v2, bge-m3), VoyageProvider (HTTP, 1024d), LocalProvider (test-only), cosine similarity, BLOB serialization
knowledge-graph.tsKuzuGraphLadybugDB (Kuzu fork) embedded graph wrapper. Schema: Entity/Memory/Community nodes, MENTIONS/RELATES_TO/SUPERSEDES/COOCCURS edges. Cypher queries, similarity search, entity CRUD
knowledge-layer.tsKnowledgeLayerUnified IKnowledgeLayer implementation: store (embed + dedup + contradiction + entity extraction + graph write), retrieve (HyDE + vector + graph expansion + MMR), entity ops, GC
retrieval-engine.tsRetrievalEngineGraph-augmented retrieval: HyDE query expansion (Haiku), multi-signal search (vector 55% + FTS 30% + graph 15%), namespace-specific decay, MMR re-ranking (λ=0.7), XML context formatting
entity-extractor.tsTwo-tier entity extraction: Tier 1 regex (DE/EN persons, orgs, tech, projects, locations), Tier 2 optional Haiku for high-value namespaces
entity-resolver.tsEntityResolverCanonical name resolution (exact → alias → normalized → create), entity merge with alias transfer
contradiction-detector.tsContradiction detection for knowledge/learnings: vector search >0.80 → heuristic checks (negation DE/EN, number change, state change)
datastore-bridge.tsDataStoreBridgeConnects DataStore tables to Knowledge Graph. registerCollection() creates collection entities, indexRecords() extracts entities from string fields via regex, findRelatedData() provides DataStore hints during retrieval. Entity type 'collection', relation type has_data_in
roles.ts4 built-in roles as a const map (researcher, creator, operator, collector). No file-based CRUD. Roles define sub-agent tool restrictions, model defaults, and system prompts (compressed single-paragraph format)
plugins.tsPluginManagerValidated import() from ~/.nodyn/plugins/node_modules/ only, NPM_NAME_RE, secrets stripped, lifecycle hooks
features.tsFeature flags (NODYN_FEATURE_* env vars), dynamic registration via registerFeature() for Pro
cost-guard.tsCostGuardBudget tracking + enforcement, cache tokens at correct rates (write 1.25x, read 0.1x)
pre-approve.tsGlob-based pattern matching for auto-approving operations in autonomous modes
pre-approve-audit.ts(deleted)Audit trail functionality consolidated into permission-guard.ts
task-manager.tsTaskManagerTask CRUD facade over RunHistory. Week summaries, briefing integration, scope-aware queries
changeset.tsChangesetManagerBackup-before-write system: copies originals to temp dir, generates unified diffs via diff -u, supports full/partial rollback. Created per-run by Session when changeset_review enabled
worker-loop.tsWorkerLoopPersistent background task executor. 60s tick interval, headless Sessions per task, AbortSignal.timeout(5min) per execution, AsyncLocalStorage per task for isolated context. Multi-turn support: wires promptUser on headless sessions so agent can ask_user — question sent as notification — promise-based pause/resume via ActiveTask.pendingInput. One-shot background tasks auto-trigger (nextRunAt=now when assignee='nodyn'). Retry with exponential backoff (1min to 30min cap). Calls runManifest() for pipeline tasks (pipeline_id set). Started on engine.init(), stopped on engine.shutdown()
cron-parser.ts5-field standard cron expression parser + shorthand interval syntax (e.g. every 5 minutes). Pure function, no external dependencies. Used by WorkerLoop for scheduled task due-time calculation
notification-router.tsNotificationRouterPluggable notification channel system. Registers NotificationChannel implementations, routes NotificationMessage to appropriate channels. Supports task completion, error, and inquiry notifications. Follow-up buttons on task completion (Details, Run again / Retry, Explain). Extension point for custom channels
telegram-notification.tsTelegramNotificationChannelImplements NotificationChannel for Telegram. Sends task results with inline follow-up buttons, handles inquiry responses for multi-turn tasks. Callback prefixes: 't:' for follow-ups, 'q:' for inquiry responses. Integrates with ActiveTask.pendingInput for pause/resume
workspace.tsWorkspace isolation for Docker: path validation, sandbox boundaries
tool-context.tsToolContext interface — shared dependency container for tool handlers. Replaces module-level closure setters. Created by Session, passed via agent.toolContext. createToolContext() factory, applyNetworkPolicy(), applyHttpRateLimits() helpers
errors.tsNodynErrorCentralized error hierarchy: NodynError (base with code + context), ValidationError, ConfigError, ExecutionError (supports Error.cause), ToolError, NotFoundError. Business-friendly error codes
input-guard.tsContent policy — scans user input before LLM (Tier 1 hard block + Tier 2 soft flag)
data-boundary.tsPrompt injection defense: wrapUntrustedData() with boundary-escape neutralization, detectInjectionAttempt() for 17 patterns (12 categories), escapeXml(). Applied to web search, HTTP, Google tools, spawn context, pipeline templates, memory extraction, briefing
output-guard.tsOutput validation: reverse shell, crypto miner, persistence mechanism detection. scanToolResult() for 8 external tools (bash, http_request, web_research, 5 Google tools). ToolCallTracker behavioral anomaly detection (read→exfil, burst HTTP, Google read→exfil, credential harvesting)
security-audit.tsSecurity event persistence to security_events SQLite table, secret masking
scope-resolver.ts3-tier scope hierarchy (Global 0.3 > Context 0.8 > User 1.0). inferScopeFromContext()
scope-classifier.tsHeuristic scope classification (no API call), multilingual (DE/EN/FR)
data-store.tsDataStoreSQLite-based structured data storage (~/.nodyn/datastore.db). Agent-defined collections, filter→SQL, aggregation, upsert
secret-vault.tsSecretVaultEncrypted SQLite vault (~/.nodyn/vault.db), AES-256-GCM, PBKDF2 600K SHA-512
secret-store.tsSecretStoreMulti-source secrets: env (NODYN_SECRET_*) > vault > config. extractSecretNames() + resolveSecretRefs()
session-budget.ts$50 session cost ceiling shared by spawn + pipeline, daily/monthly persistent caps
atomic-write.tsSync + async ensureDir() with secure permissions (0o700/0o600)
constants.tsShared numeric constants: MAX_BUFFER_BYTES, DIR_MODE_PRIVATE, FILE_MODE_PRIVATE, DEFAULT_BASH_TIMEOUT_MS
crypto-constants.tsAES-256-GCM constants: CRYPTO_ALGORITHM, CRYPTO_KEY_LENGTH, CRYPTO_IV_LENGTH, CRYPTO_TAG_LENGTH
debug-subscriber.tsDebug channel subscriber for NODYN_DEBUG output
ModuleClassPurpose
file-trigger.tsFileTriggerfs.watch + glob matching + debounce, with full tree fallback when recursive watch is unavailable

Note: http-trigger.ts, cron-trigger.ts, and git-trigger.ts have been removed. Cron scheduling is now handled by WorkerLoop + cron-parser.ts. Only file-trigger remains for filesystem watching (used by CLI --watch and watchdog.ts).

ModulePurpose
registry.tsToolRegistry with register(), registerMCP(), find(), scopedView()
permission-guard.tsisDangerous() — bash pattern + sensitive path checks, autonomy levels, pre-approval integration
builtin/bash.tsShell execution (120s timeout, 10MB buffer)
builtin/fs.tsread_file + write_file with symlink resolution
builtin/memory.tsmemory_store + memory_recall (knowledge system)
builtin/spawn.tsSub-agent spawning with role resolution, tool scoping, context injection, recursion guard
builtin/ask-user.tsInteractive prompting (select/confirm/freeform/tabbed)
builtin/batch-files.tsGlob-based rename/move/transform
builtin/http.tsHTTP requests with SSRF protection
builtin/pipeline.tsrun_pipeline — inline or stored workflow execution with parallel phases
builtin/task.tstask_create, task_update, task_list with scope validation
builtin/plan-task.tsplan_task — phased plan with workflow bridge. Auto-converts approved phases to workflow via phasesToPipelineSteps()
builtin/process.tscapture_process + promote_process — structured process capture from run_history, converts to parameterized workflows
resolve-tools.tsShared resolveTools() — 3-tier tool resolution (explicit > role > parent)
ModulePurpose
ansi.tsCentralized ANSI constants, stripAnsi(), wordWrap(), TBL box-drawing characters (shared by ui.ts and markdown.ts)
ui.tsANSI banner, styled renders (tool calls, errors, warnings, thinking)
spinner.tsBraille spinner on stderr, updateLabel() for dynamic label changes
markdown.tsStreaming markdown-to-ANSI renderer (bold, italic, code, headers, lists, tables, blockquotes, links, strikethrough)
dialog.tsInteractive prompts (select, confirm, freeform, tabbed)
diff.tsLCS-based line diff with red/green coloring, hunk-based output
footer.tsInline status line (tokens, context bar, cache %)
watchdog.tsFile change monitoring via FileTrigger
profiles.tsProfile CRUD from ~/.nodyn/profiles/
setup-wizard.tsFirst-run wizard: API key (format + live validation), model tier, optional Telegram
approval-dialog.ts3-tab pre-approval dialog (summary, risk filter, limits)
changeset-review.tsPost-run changeset review UI: colored unified diffs, Accept/Rollback/Partial flow via single keypress
ModulePurpose
types.tsManifest, ManifestStep (includes role field), AgentDef, AgentOutput, RunState, RunHooks, GateAdapter, GateDecision, GateRejectedError, GateExpiredError
validate.tsvalidateManifest() + loadManifestFile() via Zod v4
conditions.tsshouldRunStep(), evaluateCondition(), getByPath() — 7 operators (lt/gt/eq/gte/lte/exists/not_exists)
context.tsbuildStepContext() — merges global context + input_from step outputs
agent-registry.tsloadAgentDef() — dynamic ES module import with SAFE_AGENT_NAME_RE path guard
gates.tsLocalGateAdapter (prompt-fn)
runtime-adapter.tsconvertAgentTools(), wrapWithGate(), spawnViaAgent(), spawnInline() (with role support), spawnPipeline() (propagates role), spawnMock()
runner.tsrunManifest() — sequential DAG loop with conditions, gates, failure strategy

See DAG Engine for full documentation.

src/integrations/telegram/ — Telegram Bot

Section titled “src/integrations/telegram/ — Telegram Bot”

In-process Telegram bot using Telegraf. Connects directly to session.run() (no MCP needed). Three modules: telegram-bot.ts (Telegraf setup, message routing, commands), telegram-runner.ts (run lifecycle, stream handling, status edits), telegram-formatter.ts (markdown→HTML, message splitting, inline keyboards). Also serves as a notification channel for background tasks via TelegramNotificationChannel (registered with NotificationRouter).

Exposes NODYN as an MCP server with stdio and HTTP transport. Uses Engine internally — SessionStore holds per-session Session instances (not raw Agents). Includes async run polling with cursor-based event log, reply/abort flow, file attachments, buffered output limits, and persisted async run state for restart-resilient polling.

Module re-exports (Engine, Session, WorkerLoop, NotificationRouter, TelegramNotificationChannel, NotificationChannel, NotificationMessage) + CLI REPL with interactive dialog, streaming, session management, slash commands. Extension points: registerCommand() for Pro slash commands.

User Input
Session.run(task) (Session created via engine.createSession())
├──► loadConfig() ──► 3-tier merge (env > project > user)
├──► detectProjectRoot() ──► project ID (SHA-256)
├──► generateBriefing() ──► last 3-5 runs
├──► KnowledgeLayer.retrieve() ──► agent.setKnowledgeContext()
Agent.send(userMessage)
├──► _callAPI() ──► Anthropic SDK (streaming)
│ │ │
│ │ StreamProcessor
│ │ │
│ │ StreamEvents ──► CLI renders
│ │
│ ├──► retry on 429/529/5xx (3 retries, exponential backoff)
├──► stop_reason: "tool_use"
│ │
│ ▼
│ _dispatchTools() ──► Promise.allSettled()
│ │ │
│ │ WorkerPool (bash, fs)
│ │ or direct handler
│ │
│ ▼
│ tool_result ──► next iteration
├──► stop_reason: "max_tokens" + continuationPrompt
│ │
│ ▼
│ auto-continue (up to MAX_CONTINUATIONS per model: Opus 20, Sonnet 10, Haiku 5)
└──► stop_reason: "end_turn"
├──► Memory.maybeUpdate() (fire-and-forget) → nodyn:memory:store channel
├──► RunHistory.updateRun() (cost, tokens, status)
├──► storeKnowledgeEmbedding() (bounded queue, max 3 concurrent, errors logged)
Return text response
  • ESM-only: All imports use .js extensions. "type": "module" in package.json.
  • Strict TypeScript: strictest config — noUncheckedIndexedAccess, exactOptionalPropertyTypes, zero any.
  • Zero any: Use unknown + type narrowing throughout.
  • Single type file: All types in src/types/index.ts, Zod schemas in src/types/schemas.ts. Validation sets co-located with their types.
  • Beta API: All API calls use NODYN_BETAS header (token-efficient-tools-2025-02-19).
  • Error isolation: Promise.allSettled for parallel tool dispatch — one failure doesn’t kill others.
  • Cancellation: AbortController on streams, file I/O, subprocesses.
  • Proxy-safe: Thinking blocks stripped from message history (proxy signatures).
  • 3-tier config: env vars > project .nodyn/config.json > user ~/.nodyn/config.json.
  • Security-first: PROJECT_SAFE_KEYS allowlist, NPM_NAME_RE/SAFE_ROLE_NAME_RE/SAFE_PROFILE_NAME_RE validation, SSRF protection, XML escaping in RAG.
  • DRY utilities: Shared primitives in src/core/utils.ts (sha256Short, getErrorMessage, sleep) and src/cli/ansi.ts (TBL, stripAnsi, wordWrap). Constants like ALL_NAMESPACES live in src/types/index.ts.
  • Open-core extensibility: Core provides 4 extension points (Orchestrator Hooks, CLI Command Registry, Feature Flags, Notification Router) so Pro can integrate without modifying core source. See Extension Points.

Centralized error hierarchy in src/core/errors.ts. All errors extend NodynError with a machine-readable code and optional structured context.

Error ClassCodeUse Case
NodynError(base)Base class — code: string, context?: Record<string, unknown>
ValidationErrorVALIDATION_ERRORBad arguments, schema mismatch, missing required fields
ConfigErrorCONFIG_ERRORMissing config keys, invalid tiers, schema parse failures
ExecutionErrorEXECUTION_ERRORRuntime failures, API errors, timeouts. Supports Error.cause chaining
ToolErrorTOOL_ERRORTool-specific errors returned to the LLM as tool_result
NotFoundErrorNOT_FOUNDMissing pipelines, tasks, tenants, processes

All error classes are exported from @nodyn-ai/core.

TierContextDefault max_tokensUse Case
opus (thorough)1M32,000Architecture, complex reasoning, large context
sonnet (balanced)200K16,000Implementation, general tasks (default)
haiku (fast)200K8,192Transforms, knowledge extraction, classification

The thorough tier has a 1M token context window; balanced and fast tiers have 200K. All model-specific defaults (max_tokens, continuations, truncation thresholds) scale automatically via DEFAULT_MAX_TOKENS, MAX_CONTINUATIONS, and CONTEXT_WINDOW in src/types/index.ts.

ModeController, GoalTracker, and the 5-mode system have been removed. Background work is handled by WorkerLoop via task_create with scheduling fields. The --task CLI flag creates a background task directly. Pro’s sentinel/daemon/swarm modes are deprecated. See Extension Points for how Pro integrates via hooks.