Configuration
Megasthenes has two layers of configuration:
ClientConfig— shared infrastructure. Passed tonew Client(clientConfig). Holdssandboxandlogger.SessionConfig— per-session behavior. Passed toclient.connect(sessionConfig). Holdsrepo,model,maxIterations,systemPrompt,thinking,compaction, and the restoration fieldsinitialTurns/lastCompactionSummary.
For model/provider selection and per-ask overrides, see API Keys and Providers.
Connect Options (repo)
connect() takes a SessionConfig object. The repo field controls how the repository is fetched:
// Connect to a specific commit, branch, or tagawait client.connect({ repo: { url: "https://github.com/owner/repo", commitish: "v1.0.0" }, model: { provider: "anthropic", id: "claude-sonnet-4-6" }, maxIterations: 20,});
// Connect to a private repository with a tokenawait client.connect({ repo: { url: "https://github.com/owner/repo", token: process.env.GITHUB_TOKEN, }, model: { provider: "anthropic", id: "claude-sonnet-4-6" }, maxIterations: 20,});
// Explicitly specify the forge (auto-detected for github.com and gitlab.com)await client.connect({ repo: { url: "https://gitlab.example.com/owner/repo", forge: "gitlab", token: process.env.GITLAB_TOKEN, }, model: { provider: "anthropic", id: "claude-sonnet-4-6" }, maxIterations: 20,});Consuming ask() results
session.ask(prompt, options?) returns an AskStream — an AsyncIterable<StreamEvent> that also exposes .result() for the reduced TurnResult. There is no onProgress callback; consume by iterating or awaiting the result:
// Stream events as they arrivefor await (const event of session.ask("What does this repo do?")) { if (event.type === "text_delta") process.stdout.write(event.delta);}
// Or await the full reduced resultconst result = await session.ask("How are the tests structured?").result();For the full event reference and consumption patterns (mixing iteration with .result(), error events, tool events, compaction events), see Handling Responses.
Per-turn overrides
ask(prompt, options) accepts an AskOptions object for per-turn behavior. Model and thinking overrides are covered in API Keys and Providers. Other fields:
maxIterations— override the iteration cap for this turn.afterTurn— branch from a specific turn. See Multi-turn Conversations — Conversation Branching.signal— anAbortSignalto cancel the turn mid-stream.
See AskOptions for the full interface.
System Prompt
By default, megasthenes builds a system prompt that embeds the repository URL and commit SHA. Override it per session to customize behavior:
await client.connect({ repo: { url: "https://github.com/owner/repo" }, model: { provider: "anthropic", id: "claude-sonnet-4-6" }, maxIterations: 20, systemPrompt: "You are a security auditor. Focus on identifying vulnerabilities, insecure patterns, and potential attack vectors.",});You can also build the default prompt yourself and extend it:
import { Client, buildDefaultSystemPrompt } from "@nilenso/megasthenes";
const base = buildDefaultSystemPrompt("https://github.com/owner/repo", "abc123");
await client.connect({ repo: { url: "https://github.com/owner/repo" }, model: { provider: "anthropic", id: "claude-sonnet-4-6" }, maxIterations: 20, systemPrompt: `${base}\n\nAlways respond in Spanish.`,});Logging
The logger field on ClientConfig controls logging for all sessions opened by that client:
import { Client, consoleLogger, nullLogger, type Logger,} from "@nilenso/megasthenes";
// Default — consoleLoggerconst client = new Client();
// Silence all loggingconst client = new Client({ logger: nullLogger });
// Custom loggerconst customLogger: Logger = { log: (label, content) => myLogSystem.info(`${label}: ${content}`), error: (label, error) => myLogSystem.error(label, error),};const client = new Client({ logger: customLogger });Sandboxing
For production deployments or untrusted repositories, enable sandbox mode to run all operations in an isolated container:
const client = new Client({ sandbox: { baseUrl: "http://localhost:8080", timeoutMs: 120_000, secret: "optional-auth-secret", },});When enabled, repository cloning and all tool execution (file reads, searches, git operations) happen inside the sandbox. The host filesystem is never accessed directly.
See the Sandboxed Execution guide for security layers, architecture, and how to run the sandbox server.
Thinking
Control the model’s reasoning behavior via the thinking field on SessionConfig. Megasthenes supports two modes:
- Effort-based (cross-provider): Set an effort level that pi-ai maps to each provider’s native format (
reasoning.effortfor OpenAI,thinkingfor Anthropic, etc.). - Adaptive (Anthropic 4.6 only): The model decides when and how much to think per request.
// OpenAI — effort-based reasoningawait client.connect({ repo: { url: "https://github.com/owner/repo" }, model: { provider: "openai", id: "o3" }, maxIterations: 20, thinking: { effort: "low" },});
// Anthropic 4.5 — effort-based (older model, no adaptive support)await client.connect({ repo: { url: "https://github.com/owner/repo" }, model: { provider: "anthropic", id: "claude-sonnet-4-5-20251022" }, maxIterations: 20, thinking: { effort: "high", budgetOverrides: { high: 10000 } },});
// Anthropic 4.6 — adaptive (model decides when/how much to think)await client.connect({ repo: { url: "https://github.com/owner/repo" }, model: { provider: "anthropic", id: "claude-sonnet-4-6" }, maxIterations: 20, thinking: { type: "adaptive" },});
// Anthropic 4.6 — adaptive with explicit effort guidanceawait client.connect({ repo: { url: "https://github.com/owner/repo" }, model: { provider: "anthropic", id: "claude-sonnet-4-6" }, maxIterations: 20, thinking: { type: "adaptive", effort: "medium" },});| Field | Type | Description |
|---|---|---|
type | "adaptive" | Anthropic 4.6 only. Omit for effort-based mode. |
effort | "minimal" | "low" | "medium" | "high" | "xhigh" | Required for effort-based, optional for adaptive. |
budgetOverrides | ThinkingBudgets | Custom token budgets per level (effort-based only). |
Thinking can also be overridden per ask() — see API Keys and Providers.
Context Compaction
When conversations grow long, megasthenes automatically summarizes older messages to stay within the model’s context window. Compaction is enabled by default — set compaction: { enabled: false } to opt out, or override individual fields to tune when it fires. Any fields you leave unset fall back to the defaults shown below.
// Opt out entirelyawait client.connect({ repo: { url: "https://github.com/owner/repo" }, model: { provider: "anthropic", id: "claude-sonnet-4-6" }, maxIterations: 20, compaction: { enabled: false },});
// Tune the thresholds (e.g. for a 1M-context model)await client.connect({ repo: { url: "https://github.com/owner/repo" }, model: { provider: "anthropic", id: "claude-sonnet-4-6" }, maxIterations: 20, compaction: { enabled: true, // default: true contextWindow: 1_000_000, // default: 200_000 — total usable context reserveTokens: 16_384, // default: 16_384 — tokens held back for the response keepRecentTokens: 20_000, // default: 20_000 — recent messages kept unsummarized },});Compaction fires when estimated context tokens exceed contextWindow - reserveTokens. The most recent messages totalling roughly keepRecentTokens are retained verbatim; everything before that is replaced with an LLM-generated summary. See the compaction event in Handling Responses for how to observe it at runtime.
Tracing
megasthenes emits OpenTelemetry spans following the GenAI semantic conventions. The library depends only on @opentelemetry/api — if no OTel SDK is installed, all tracing is a zero-overhead no-op.
To send traces to any OTel-compatible backend (Jaeger, Honeycomb, Langfuse, etc.):
- Install
@opentelemetry/sdk-nodeand your backend’s exporter or span processor - Create and start a
NodeSDKinstance before creating anyClient - All
session.ask()calls will automatically emit spans to your backend
Tracing currently covers the connected session lifecycle. The main spans emitted today are:
askconnectrepo.clone_or_fetchrepo.resolve_commitishrepo.create_worktreeask.turncompactiongen_ai.chatgen_ai.execute_tool
Console (development)
import { NodeSDK } from "@opentelemetry/sdk-node";import { ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
const sdk = new NodeSDK({ traceExporter: new ConsoleSpanExporter() });sdk.start();Integrating with an observability platform
Most LLM observability platforms (Arize Phoenix, Langfuse, Honeycomb, Jaeger) accept OTLP, so any OTLP exporter will work. The example below ships traces to a local Arize Phoenix instance:
import { NodeSDK } from "@opentelemetry/sdk-node";import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
const sdk = new NodeSDK({ spanProcessors: [ new BatchSpanProcessor( new OTLPTraceExporter({ url: "http://localhost:6006/v1/traces", }), ), ],});sdk.start();Phoenix reads the OpenInference semantic conventions, while megasthenes emits OTel GenAI spans. Traces will arrive with the basic export above, but Phoenix’s built-in token-count, cost, and input/output panels stay empty until you map between the two. See scripts/ask-with-phoenix.ts for a worked SpanProcessor that enriches spans with the OpenInference attributes Phoenix expects.
See the Observability guide for the full span hierarchy, emitted attributes/events, and structured error tracing.