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.