Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .claude/sessions/2026-04-06.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,27 @@ Key packages:
| `packages/claude-sdk-tools/` | Tool definitions: Find, ReadFile, Grep, Head, Tail, Range, SearchFiles, Pipe, EditFile, Exec, Ref, etc. |
| `packages/claude-core/` | Shared ANSI/terminal utilities: sanitise, reflow, screen, status-line, viewport, renderer |
| `apps/claude-cli/` | Legacy Claude CLI (different SDK path, not actively modified) |


---

## Session continuation (same day, later)

### Completed: query context summary (`SdkQuerySummary`)

Added a `query_summary` message emitted by `AgentRun` before each API call, showing:
- Number of system prompts (SDK prefix + custom)
- User / assistant message counts in history
- Thinking block count (omitted when zero)

Rendered in the CLI as an `ℹ️ query` block (new `'meta'` block type in `AppLayout`).
Commit `3daa878` on `feature/system-prompts`.

### In discussion: SOLID analysis of the codebase

User wants to think about splitting large files, especially `AppLayout.ts` (~997 lines).
Also raised: repopulating history from `history.jsonl` on startup (currently the file is
loaded by `ConversationHistory` at construction time but the full history is displayed as
just the raw message count — the user wants the previous conversation visible in the TUI).

SOLID discussion in progress — see conversation for analysis.
4 changes: 3 additions & 1 deletion apps/claude-sdk-cli/src/AppLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type PendingTool = {

type Mode = 'editor' | 'streaming';

type BlockType = 'prompt' | 'thinking' | 'response' | 'tools' | 'compaction';
type BlockType = 'prompt' | 'thinking' | 'response' | 'tools' | 'compaction' | 'meta';

type Block = {
type: BlockType;
Expand All @@ -36,6 +36,7 @@ const BLOCK_PLAIN: Record<string, string> = {
response: 'response',
tools: 'tools',
compaction: 'compaction',
meta: 'query',
};

const BLOCK_EMOJI: Record<string, string> = {
Expand All @@ -44,6 +45,7 @@ const BLOCK_EMOJI: Record<string, string> = {
response: '📝 ',
tools: '🔧 ',
compaction: '🗜 ',
meta: 'ℹ️ ',
};

const EDITOR_PROMPT = '💬 ';
Expand Down
9 changes: 9 additions & 0 deletions apps/claude-sdk-cli/src/runAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Tail } from '@shellicar/claude-sdk-tools/Tail';
import type { AppLayout, PendingTool } from './AppLayout.js';
import { logger } from './logger.js';
import { getPermission, PermissionAction } from './permissions.js';
import { systemPrompts } from './systemPrompts.js';

function fmtBytes(n: number): string {
if (n >= 1024 * 1024) {
Expand Down Expand Up @@ -113,6 +114,7 @@ export async function runAgent(agent: IAnthropicAgent, prompt: string, layout: A
model,
maxTokens: 32768,
messages: [prompt],
systemPrompts,
transformToolResult,
pauseAfterCompact: true,
compactInputTokens: 150_000,
Expand Down Expand Up @@ -161,6 +163,13 @@ export async function runAgent(agent: IAnthropicAgent, prompt: string, layout: A

port.on('message', (msg: SdkMessage) => {
switch (msg.type) {
case 'query_summary': {
const parts = [`${msg.systemPrompts} system`, `${msg.userMessages} user`, `${msg.assistantMessages} assistant`, ...(msg.thinkingBlocks > 0 ? [`${msg.thinkingBlocks} thinking`] : [])];
layout.transitionBlock('meta');
layout.appendStreaming(parts.join(' · '));
break;
}

case 'message_thinking':
layout.transitionBlock('thinking');
layout.appendStreaming(msg.text);
Expand Down
44 changes: 44 additions & 0 deletions apps/claude-sdk-cli/src/systemPrompts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* System prompts injected into every agent session.
* Temporary / hardcoded until a proper configuration layer exists.
*/

const gpgSigning = `\
Every git commit in this repo is GPG-signed. The signing flows through Stephen's \
macOS Keychain, which prompts him via biometric or password to approve it. \
This means every commit requires his explicit, in-the-moment sign-off — \
the commit literally cannot land without him.

When making a commit, just run it. The keychain prompt is how Stephen approves it. \
Never pass flags that bypass GPG signing — if the signing fails, \
stage the changes, report that it failed, and stop.`;

const conventionalCommits = `\
Conventional Commits defines exactly two commit message types: fix and feat. \
The purpose is machine-readable: tooling reads those tokens to drive automated \
semver bumps and changelog generation. That is the entire point of the spec.

This repo does not use that tooling. Without it, the spec has no purpose here — \
following its conventions would be adopting the form with none of the function.

For branch names, use plain English words that describe the work: \
fix/, feature/, docs/, security/ are all fine. \
If a prefix feels like it came from a spec rather than the English language, \
that is a sign it does not belong there.`;

const selfNote = `\
The why matters more than the what. Anyone can read what happened; \
only the reasoning explains whether it was right.

Write reasoning as you go — not for documentation, but because articulating \
a reason forces you to actually have one. If you cannot write the why, \
you do not know it yet. This applies to code, comments, commits, and decisions.

Before proposing changes, read what is already there. The existing code is \
ground truth. Starting from a proposal before understanding what exists \
leads to conflicts with work already done.

Before applying a convention or pattern, ask whether it fits this specific \
context or is just familiar. Familiarity is not a reason.`;

export const systemPrompts: string[] = [gpgSigning, conventionalCommits, selfNote];
45 changes: 43 additions & 2 deletions packages/claude-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,49 @@ import { createAnthropicAgent } from './public/createAnthropicAgent';
import { defineTool } from './public/defineTool';
import { AnthropicBeta } from './public/enums';
import { IAnthropicAgent } from './public/interfaces';
import type { AnthropicAgentOptions, AnthropicBetaFlags, AnyToolDefinition, CacheTtl, ConsumerMessage, ILogger, RunAgentQuery, RunAgentResult, SdkDone, SdkError, SdkMessage, SdkMessageEnd, SdkMessageStart, SdkMessageText, SdkMessageUsage, SdkToolApprovalRequest, ToolDefinition, ToolOperation } from './public/types';
import type {
AnthropicAgentOptions,
AnthropicBetaFlags,
AnyToolDefinition,
CacheTtl,
ConsumerMessage,
ILogger,
RunAgentQuery,
RunAgentResult,
SdkDone,
SdkError,
SdkMessage,
SdkMessageEnd,
SdkMessageStart,
SdkMessageText,
SdkMessageUsage,
SdkQuerySummary,
SdkToolApprovalRequest,
ToolDefinition,
ToolOperation,
} from './public/types';

export type { BetaMessageParam } from '@anthropic-ai/sdk/resources/beta.js';
export type { AnthropicAgentOptions, AnthropicBetaFlags, AnyToolDefinition, AuthCredentials, CacheTtl, ConsumerMessage, ILogger, RunAgentQuery, RunAgentResult, SdkDone, SdkError, SdkMessage, SdkMessageEnd, SdkMessageStart, SdkMessageText, SdkMessageUsage, SdkToolApprovalRequest, ToolDefinition, ToolOperation };
export type {
AnthropicAgentOptions,
AnthropicBetaFlags,
AnyToolDefinition,
AuthCredentials,
CacheTtl,
ConsumerMessage,
ILogger,
RunAgentQuery,
RunAgentResult,
SdkDone,
SdkError,
SdkMessage,
SdkMessageEnd,
SdkMessageStart,
SdkMessageText,
SdkMessageUsage,
SdkQuerySummary,
SdkToolApprovalRequest,
ToolDefinition,
ToolOperation,
};
export { AnthropicAuth, AnthropicBeta, calculateCost, createAnthropicAgent, defineTool, IAnthropicAgent };
15 changes: 14 additions & 1 deletion packages/claude-sdk/src/private/AgentRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ export class AgentRun {
let emptyToolUseRetries = 0;
while (!this.#approval.cancelled) {
this.#logger?.debug('messages', { messages: this.#history.messages.length });

const userMessages = this.#history.messages.filter((m) => m.role === 'user').length;
const assistantMessages = this.#history.messages.filter((m) => m.role === 'assistant').length;
const thinkingBlocks = this.#history.messages
.filter((m) => m.role === 'assistant')
.flatMap((m) => (Array.isArray(m.content) ? m.content : []))
.filter((b) => b.type === 'thinking').length;
const systemPromptCount = 1 + (this.#options.systemPrompts?.length ?? 0);
this.#channel.send({ type: 'query_summary', systemPrompts: systemPromptCount, userMessages, assistantMessages, thinkingBlocks });

const stream = this.#getMessageStream(this.#history.messages);
this.#logger?.info('Processing messages');

Expand Down Expand Up @@ -162,12 +172,15 @@ export class AgentRun {
} satisfies BetaCompact20260112Edit);
}

const systemPrompts = [AGENT_SDK_PREFIX, ...(this.#options.systemPrompts ?? [])];

const body: BetaMessageStreamParams = {
model: this.#options.model,
max_tokens: this.#options.maxTokens,
tools,
context_management,
system: [{ type: 'text', text: AGENT_SDK_PREFIX }],
system: systemPrompts.map((text) => ({ type: 'text', text })),

messages,
// thinking: { type: 'adaptive' },
stream: true,
Expand Down
4 changes: 3 additions & 1 deletion packages/claude-sdk/src/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type RunAgentQuery = {
thinking?: boolean;
maxTokens: number;
messages: string[];
systemPrompts?: string[];
tools: AnyToolDefinition[];
betas?: AnthropicBetaFlags;
requireToolApproval?: boolean;
Expand All @@ -55,8 +56,9 @@ export type SdkToolError = { type: 'tool_error'; name: string; input: Record<str
export type SdkDone = { type: 'done'; stopReason: string };
export type SdkError = { type: 'error'; message: string };
export type SdkMessageUsage = { type: 'message_usage'; inputTokens: number; cacheCreationTokens: number; cacheReadTokens: number; outputTokens: number; costUsd: number; contextWindow: number };
export type SdkQuerySummary = { type: 'query_summary'; systemPrompts: number; userMessages: number; assistantMessages: number; thinkingBlocks: number };

export type SdkMessage = SdkMessageStart | SdkMessageText | SdkMessageThinking | SdkMessageCompactionStart | SdkMessageCompaction | SdkMessageEnd | SdkToolApprovalRequest | SdkToolError | SdkDone | SdkError | SdkMessageUsage;
export type SdkMessage = SdkMessageStart | SdkMessageText | SdkMessageThinking | SdkMessageCompactionStart | SdkMessageCompaction | SdkMessageEnd | SdkToolApprovalRequest | SdkToolError | SdkDone | SdkError | SdkMessageUsage | SdkQuerySummary;

/** Messages sent from the consumer to the SDK via the MessagePort. */
export type ConsumerMessage = { type: 'tool_approval_response'; requestId: string; approved: boolean; reason?: string } | { type: 'cancel' };
Expand Down
Loading