Skip to main content
Version: 0.4.0

Source Code Reference

Entrypoints​

src/index.ts​

Validates configuration, then wires a readline interface reading one JSON-RPC line at a time from stdin into AcpServer.handleInput(), writing the response line to stdout. This is the raw ACP stdin/stdout server (npm run start).

src/cli.ts​

Standalone interactive REPL. Generates a cli-session-<random> thread ID, then loops: prompt the user, call runGraphWorkflow("supervisor", input, threadId, progressCallback), print the response, repeat until exit/quit. Progress callbacks are written to stderr.

src/server.ts​

Express app (npm run server). See the endpoint table in Server Usage. Internally uses an EventEmitter (runEvents) to fan out per-run progress/complete events to any connected SSE clients, and an in-memory Map (activeRuns) to track run status for clients that connect to the stream after a run has already completed.

src/mcp.ts​

MCP server (npm run mcp) built on @modelcontextprotocol/sdk. Registers a single run_agent tool (see MCP Usage) and serves it over StdioServerTransport.

src/graph-sdk.ts​

Re-exports graph, runGraphWorkflow, SQLiteCheckpointer, and the AgentWorkspaceState type for consumption as a library (pinky-and-the-brain/sdk export) by other Node.js projects that want to embed the graph engine directly.

Agents (src/agents/)​

See Agent Flow for how these fit together.

types.ts​

Defines AgentWorkspaceState (the run's state shape, see Architecture), AgentProgress, and BrainAgent - a deliberately narrow interface over the compiled agent, because createDeepAgent's inferred type references zod types nested inside deepagents/node_modules that TypeScript cannot name from this project (TS2742).

agent.ts​

Exports createBrainAgent(), which assembles the Deep Agent from createChatModel(), the tools, the system prompt (with the built-in Deep Agents base prompt removed via base: null), and the SQLiteCheckpointer. Also exports the checkpointer singleton.

graph.ts​

The compatibility layer. Exports runGraphWorkflow(agentName, prompt, threadId, progressCallback) - the single function called by every entrypoint - plus the checkpointer and a lazily-constructed graph (a proxy over getGraph(), so importing this module never requires an API key). It returns the run's final state with instructorState.explanation set to the latest assistant reply.

persona.ts​

Exports BRAIN_PERSONA_PROMPT: The Brain's voice, catchphrases, and the rule that technical accuracy always outranks theatrics.

prompts.ts​

Exports JOURNEY_PROMPT (the conversation state machine and the teaching loop) and BRAIN_SYSTEM_PROMPT (persona + journey).

tools.ts​

Exports brainTools - list_topics, list_subtopics, retrieve_content, save_article, update_article, read_article, publish_article, export_article - along with the TOPICS registry and the functions behind the tools: retrieveContext(query, area, limit, { mode }) (BM25 fused with vector similarity; see Retrieval), formatPassages() (which labels each passage with the document and section it came from), loadKnowledgeStore(), loadEmbeddingStore(), extractSubtopics(area), resolveTopic(), and resolveArticlePath() (which confines model-chosen filenames to ./articles/).

retrieveContext is async, because the vector half embeds the query. Its mode argument exists only so the evaluation harness can score both retrievers in one process; the agent leaves it alone and takes the configured default.

Protocol (src/protocol/)​

acp-server.ts​

AcpServer class implementing the ACP JSON-RPC methods; see ACP Protocol for the full method/error-code reference.

messages.ts​

Zod schemas for JSON-RPC requests and every ACP method's params (InitializeParamsSchema, SessionNewParamsSchema, SessionPromptParamsSchema, RunAgentParamsSchema).

Storage (src/storage/)​

sqlite.ts​

SQLiteCheckpointer implementing LangGraph's BaseCheckpointSaver on top of the sqlite3 package, with WAL-mode PRAGMAs for performance. See Architecture.

s3.ts​

S3Wrapper providing uploadState/downloadState/deleteState, backed by @aws-sdk/client-s3 or an in-memory Map fallback.

Utils (src/utils/)​

retrieval.ts​

The lexical retriever, and pure: no I/O, no config, no knowledge of where chunks come from. chunkId() (content-addressed identity), tokenize(), stem(), STOPWORDS, buildIndex(), search() (BM25) and fuseRankings() (reciprocal rank fusion). Every rule in it fixes a measured defect of the substring-counting scorer it replaced — see Retrieval.

embeddings.ts​

The vector half, and soft-failing throughout: embed() (Gemini, with backoff on a 429), embedQuery() (memoised, since a teaching loop retrieves the same subtopic repeatedly), normalise(), quantise(), cosine(), and encodeStore() / decodeStore() for the binary embeddings.bin format. A missing key, a refusal or a network error returns null and retrieval continues on BM25 alone.

model.ts​

createChatModel() - returns a ChatAnthropic using ANTHROPIC_API_KEY and config.anthropicModel (default claude-sonnet-5), or throws if the key is missing. Anthropic is the only supported provider. No temperature is sent, because Claude Sonnet 5 rejects the parameter.

messages.ts​

isAIMessage, isHumanMessage, getMessageContent - defensive type guards/content extractors that work for both live LangChain message class instances and deserialized plain objects (e.g. after a checkpoint round-trip).

blog-mcp.ts​

The blog's own publishing tools, reached over MCP. withBlogSession() spawns the articles server that ships inside the thiagocolen.github.io checkout named by BLOG_REPO_PATH, runs a callback against it, and always closes the child process — one session per publish, since the server holds a git worktree open.

resolveBlogRepoPath() fails with an actionable message when the checkout is missing or is on a branch without mcp-server/. Tool results are unwrapped by a helper that turns the MCP isError flag into a thrown BlogToolError, because the SDK resolves rather than rejects on it — without that, failure text would be parsed as success.

__setBlogSession / __resetBlogSession are the test seam: they replace the whole session, so tests never spawn a process.

image-gen.ts​

Cover and figure generation, the one place Google appears in an otherwise Anthropic-only agent — Claude does not generate images, and nothing in the reasoning or writing path goes through here. generateCoverImage() and generateBodyImage() build a prompt (subject first, then the article's style, then the no-text and full-bleed clauses) and request it at a fixed aspect ratio — covers 1:1, figures 16:9 — and the single pixel tier in GEMINI_IMAGE_SIZE. A cover ends up larger than a figure because a square frame holds more pixels than a wide one, not because it asks for more: which tiers exist is a property of the model, and the default one refuses both 512 and 2K.

Every failure is soft — a missing key, a refusal, a network error and an empty response all return null, and the article publishes without the picture. Because that hides a refused imageSize just as thoroughly as a network blip, a failed request is retried once at 1K before it gives up.

__setImageGenerator / __resetImageGenerator are the test seam, replacing the model call so tests never reach the network.

illustration-styles.ts​

The ten styles an article can be drawn in, and styleFor(), which hashes a title (FNV-1a) to exactly one of them. One style per article, shared by its cover and all its figures. Deterministic on purpose: republishing an article already under review must not redraw it in a different style. Keyed on the title rather than the slug, because the blog does not report a slug until the draft exists — by which point the images are generated.

logger.ts​

Centralized logger writing colorized console output plus a plain-text agent.log file in the project root, gated by the LOG_LEVEL environment variable (DEBUG < INFO < WARN < ERROR, default INFO).

Scripts​

src/scripts/ingest.ts​

Walks the curated corpus (CURATED_CONTENT_PATH), splits files into paragraph-level chunks, and writes them into src/storage/knowledge-store.json tagged with an area, a content-addressed id, the source file and the nearest heading. Duplicate paragraphs collapse by id. When a Gemini key is present it also embeds every chunk that does not already have a vector and writes src/storage/embeddings.bin — resumable, because reuse is keyed on that id. See Data Ingestion for the full pipeline, and Retrieval for how the artifacts are read back.