Skip to content

Commit 45acd14

Browse files
committed
feat(core): session bootstrap, quality pipeline non-blocking, trimmed memory injection
Quality pipeline UX: - Replace cleanupUI() with inkRenderer.pause()/resume() around quality checks - Composer now flickers briefly instead of disappearing for seconds Session bootstrap: - Add generateSessionBootstrap() + injectSessionBootstrap() methods - Injects explicit [Session Bootstrap] system note on every session start, /new, /clear, and resumed sessions - Surfaces top 3 memories, AGENTS.md summary, active skills, key project files Memory efficiency: - Trim getContextMemories() default from 20 entries to 5 - Saves ~500-1000 tokens per turn; older memories accessible via recall_memory
1 parent 3a44d02 commit 45acd14

2 files changed

Lines changed: 91 additions & 9 deletions

File tree

src/core/agent.ts

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,10 @@ export class AutohandAgent {
10811081
llm: this.llm,
10821082
workspaceRoot: runtime.workspaceRoot,
10831083
model: model,
1084-
resetConversation: async () => this.resetConversationContext(),
1084+
resetConversation: async () => {
1085+
await this.resetConversationContext();
1086+
await this.injectSessionBootstrap();
1087+
},
10851088
undoFileMutation: () => this.files.undoLast(),
10861089
removeLastTurn: () => this.conversation.removeLastTurn(),
10871090
// Status command context
@@ -1344,6 +1347,10 @@ export class AutohandAgent {
13441347
this.sessionManager.createSession(this.runtime.workspaceRoot, model),
13451348
]);
13461349

1350+
// Inject explicit session bootstrap so the LLM is consciously aware of
1351+
// memories, AGENTS.md, skills, and project context from the first turn.
1352+
await this.injectSessionBootstrap();
1353+
13471354
// Phase 3: Telemetry (no stdout output)
13481355
if (session) {
13491356
await this.telemetryManager.startSession(
@@ -1408,6 +1415,8 @@ export class AutohandAgent {
14081415
this.sessionManager.createSession(this.runtime.workspaceRoot, model),
14091416
]);
14101417

1418+
await this.injectSessionBootstrap();
1419+
14111420
// Start telemetry session
14121421
if (session) {
14131422
await this.telemetryManager.startSession(
@@ -1531,6 +1540,7 @@ If lint or tests fail, report the issues but do NOT commit.`;
15311540
const session = await this.sessionManager.loadSession(sessionId);
15321541

15331542
await this.resetConversationContext();
1543+
await this.injectSessionBootstrap();
15341544
const messages = session.getMessages();
15351545
for (const msg of messages) {
15361546
if (msg.role === 'system') {
@@ -2541,14 +2551,19 @@ If lint or tests fail, report the issues but do NOT commit.`;
25412551
this.persistentInput.stop();
25422552
this.persistentInputActiveTurn = false;
25432553
}
2544-
// Stop Ink renderer if active — it holds stdin/stdout and will
2545-
// swallow quality check output or cause stdin conflicts with spawn.
2546-
if (this.useInkRenderer) {
2547-
this.cleanupUI();
2554+
// Pause Ink renderer instead of destroying it. This releases stdin/stdout
2555+
// so spawned child processes (lint, test) work correctly, but preserves
2556+
// state so the composer reappears immediately after quality checks.
2557+
if (this.useInkRenderer && this.inkRenderer) {
2558+
this.inkRenderer.pause();
25482559
}
25492560
cleanupConsoleBridge();
25502561
cleanupConsoleBridge = () => {}; // Prevent double-cleanup in finally
25512562
await this.runQualityPipeline();
2563+
// Resume Ink so the composer is restored before runInstruction returns.
2564+
if (this.useInkRenderer && this.inkRenderer) {
2565+
this.inkRenderer.resume();
2566+
}
25522567
}
25532568
} catch (error) {
25542569
success = false;
@@ -6488,6 +6503,71 @@ If lint or tests fail, report the issues but do NOT commit.`;
64886503
this.updateContextUsage(this.conversation.history());
64896504
}
64906505

6506+
/**
6507+
* Generate an explicit session bootstrap note that surfaces the most
6508+
* important context — memories, AGENTS.md, skills, and project structure —
6509+
* as a coherent "here's what you should know" block. This is injected as a
6510+
* system note so the LLM explicitly sees it, rather than passively hoping it
6511+
* notices buried system prompt content.
6512+
*/
6513+
private async generateSessionBootstrap(): Promise<string> {
6514+
const parts: string[] = ['[Session Bootstrap]'];
6515+
6516+
// 1. Top memories (most relevant, limited to save tokens)
6517+
const memories = await this.memoryManager.getContextMemories(3);
6518+
if (memories) {
6519+
parts.push('', '## Memories & Preferences', memories);
6520+
}
6521+
6522+
// 2. AGENTS.md summary (first 20 lines — enough for conventions, not the full manifesto)
6523+
const agentsPath = path.join(this.runtime.workspaceRoot, 'AGENTS.md');
6524+
if (await fs.pathExists(agentsPath)) {
6525+
const content = await fs.readFile(agentsPath, 'utf-8');
6526+
const summary = content.split('\n').slice(0, 20).join('\n');
6527+
if (summary.trim()) {
6528+
parts.push('', '## Project Instructions (AGENTS.md)', summary);
6529+
}
6530+
}
6531+
6532+
// 3. Active skills
6533+
const activeSkills = this.skillsRegistry.getActiveSkills();
6534+
if (activeSkills.length > 0) {
6535+
parts.push('', '## Active Skills');
6536+
for (const skill of activeSkills) {
6537+
parts.push(`- **${skill.name}**: ${skill.description}`);
6538+
}
6539+
}
6540+
6541+
// 4. Lightweight project scan — key config files and top-level structure
6542+
const keyFiles = ['package.json', 'README.md', 'tsconfig.json', ' Cargo.toml', 'pyproject.toml', 'go.mod'];
6543+
const foundKeys: string[] = [];
6544+
for (const file of keyFiles) {
6545+
if (await fs.pathExists(path.join(this.runtime.workspaceRoot, file.trim()))) {
6546+
foundKeys.push(file.trim());
6547+
}
6548+
}
6549+
if (foundKeys.length > 0) {
6550+
parts.push('', `## Project Structure`, `Key files detected: ${foundKeys.join(', ')}`);
6551+
}
6552+
6553+
return parts.join('\n');
6554+
}
6555+
6556+
/**
6557+
* Inject the session bootstrap into the conversation. Called once per
6558+
* session start (new CLI invocation, /new, /clear, or resumed session).
6559+
*/
6560+
private async injectSessionBootstrap(): Promise<void> {
6561+
try {
6562+
const bootstrap = await this.generateSessionBootstrap();
6563+
if (bootstrap && bootstrap.length > '[Session Bootstrap]'.length + 10) {
6564+
this.conversation.addSystemNote(bootstrap, '[Session Bootstrap]');
6565+
}
6566+
} catch {
6567+
// Bootstrap is best-effort; never block session start
6568+
}
6569+
}
6570+
64916571
private availableProviders(): ProviderName[] {
64926572
const providers: ProviderName[] = [];
64936573
if (this.runtime.config.openrouter) providers.push('openrouter');

src/memory/MemoryManager.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,22 +212,24 @@ export class MemoryManager {
212212
}
213213

214214
/**
215-
* Get memories formatted for LLM context injection
215+
* Get memories formatted for LLM context injection.
216+
* Limits to the most recent/relevant entries to avoid consuming excessive
217+
* system prompt tokens. Older memories remain accessible via recall_memory.
216218
*/
217-
async getContextMemories(): Promise<string> {
219+
async getContextMemories(limit = 5): Promise<string> {
218220
const { project, user } = await this.listAll();
219221
const parts: string[] = [];
220222

221223
if (project.length > 0) {
222224
parts.push('## Project Memories');
223-
for (const entry of project.slice(0, 10)) {
225+
for (const entry of project.slice(0, limit)) {
224226
parts.push(`- ${entry.content}`);
225227
}
226228
}
227229

228230
if (user.length > 0) {
229231
parts.push('## User Preferences');
230-
for (const entry of user.slice(0, 10)) {
232+
for (const entry of user.slice(0, limit)) {
231233
parts.push(`- ${entry.content}`);
232234
}
233235
}

0 commit comments

Comments
 (0)