Skip to content

Commit 93ca9d1

Browse files
committed
feat(agy): add configurable prompt ordering
1 parent a264356 commit 93ca9d1

4 files changed

Lines changed: 62 additions & 8 deletions

File tree

src/agent/agy-prompt.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export type AgyPromptOrder = 'task-first' | 'context-first';
2+
3+
export function resolveAgyPromptOrder(value: unknown): AgyPromptOrder {
4+
return value === 'context-first' ? 'context-first' : 'task-first';
5+
}
6+
7+
export function composeAgyPrompt(
8+
taskPrompt: string,
9+
systemPrompt: string,
10+
order: AgyPromptOrder = 'task-first',
11+
): string {
12+
if (!systemPrompt) return taskPrompt;
13+
14+
const operationalContext = [
15+
'[Operational Context — cli-jaw Integration]',
16+
'The following operational guidelines apply to this session. Follow these task rules and use the tools/commands described:',
17+
'',
18+
systemPrompt,
19+
].join('\n');
20+
const currentTask = `[Current cli-jaw task]\n${taskPrompt}`;
21+
22+
return order === 'context-first'
23+
? `${operationalContext}\n\n---\n\n${currentTask}`
24+
: `${currentTask}\n\n---\n\n${operationalContext}`;
25+
}

src/agent/spawn.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import {
6868
stripAgyResumeReplayPrefixes,
6969
} from './agy-runtime.js';
7070
import { startAgyTranscriptWatcher, type AgyTranscriptWatcherHandle } from './agy-transcript-watcher.js';
71+
import { composeAgyPrompt, resolveAgyPromptOrder } from './agy-prompt.js';
7172
import { appendAssistantTextSegment, normalizeAssistantDisplayText, pushTrace } from './events/helpers.js';
7273
import { listKiroConversationIdsForCwd } from './kiro-auth.js';
7374
import {
@@ -1219,7 +1220,11 @@ export function spawnAgent(prompt: string, opts: SpawnOpts = {}): SpawnResult {
12191220
? withHistoryPrompt(prompt, historyBlock)
12201221
: prompt;
12211222
if (cli === 'agy' && sysPrompt) {
1222-
promptForArgs = `[Current cli-jaw task]\n${promptForArgs}\n\n---\n\n[Operational Context — cli-jaw Integration]\nThe following operational guidelines apply to this session. Follow these task rules and use the tools/commands described:\n\n${sysPrompt}`;
1223+
promptForArgs = composeAgyPrompt(
1224+
promptForArgs,
1225+
sysPrompt,
1226+
resolveAgyPromptOrder(cfg.promptOrder),
1227+
);
12231228
} else if ((cli === 'kiro-code' || (cli === 'ai-e' && effectiveProvider === 'kiro')) && sysPrompt) {
12241229
promptForArgs = `[Operational Context — cli-jaw Integration]\nThe following operational guidelines apply to this session. Follow these task rules and use the tools/commands described:\n\n${sysPrompt}\n\n---\n\n${promptForArgs}`;
12251230
}

tests/unit/agy-prompt.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import assert from 'node:assert/strict';
2+
import test from 'node:test';
3+
import { composeAgyPrompt, resolveAgyPromptOrder } from '../../src/agent/agy-prompt.js';
4+
5+
6+
test('AGY-PROMPT-001: task-first remains the compatibility default', () => {
7+
const prompt = composeAgyPrompt('CURRENT', 'SYSTEM');
8+
assert.ok(prompt.startsWith('[Current cli-jaw task]\nCURRENT'));
9+
assert.ok(prompt.endsWith('SYSTEM'));
10+
});
11+
12+
test('AGY-PROMPT-002: context-first keeps stable instructions first and current task last', () => {
13+
const prompt = composeAgyPrompt('HISTORY\nCURRENT', 'SYSTEM', 'context-first');
14+
assert.ok(prompt.startsWith('[Operational Context — cli-jaw Integration]'));
15+
assert.ok(prompt.indexOf('SYSTEM') < prompt.indexOf('HISTORY'));
16+
assert.ok(prompt.endsWith('[Current cli-jaw task]\nHISTORY\nCURRENT'));
17+
});
18+
19+
test('AGY-PROMPT-003: unknown settings fail closed to task-first', () => {
20+
assert.equal(resolveAgyPromptOrder('context-first'), 'context-first');
21+
assert.equal(resolveAgyPromptOrder('task-first'), 'task-first');
22+
assert.equal(resolveAgyPromptOrder('unknown'), 'task-first');
23+
assert.equal(resolveAgyPromptOrder(undefined), 'task-first');
24+
});
25+
26+
test('AGY-PROMPT-004: no system prompt leaves the task untouched', () => {
27+
assert.equal(composeAgyPrompt('CURRENT', '', 'context-first'), 'CURRENT');
28+
});

tests/unit/steer-flow.test.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,20 +133,16 @@ test('SF-004b: resume argv CLIs keep enriched promptForArgs for agy and compact
133133
);
134134
});
135135

136-
test('SF-004c: agy front-loads current task before operational context', () => {
136+
test('SF-004c: agy delegates configurable prompt ordering to the bounded composer', () => {
137137
const src = fs.readFileSync(join(__dirname, '../../src/agent/spawn.ts'), 'utf8');
138138
const agyBranchIdx = src.indexOf("if (cli === 'agy' && sysPrompt)");
139139
const kiroBranchIdx = src.indexOf("else if ((cli === 'kiro-code'");
140140
assert.ok(agyBranchIdx > 0, 'agy should have a dedicated prompt ordering branch');
141141
assert.ok(kiroBranchIdx > agyBranchIdx, 'kiro branch should remain separate from agy');
142142

143143
const agyBranch = src.slice(agyBranchIdx, kiroBranchIdx);
144-
assert.ok(agyBranch.includes('[Current cli-jaw task]'), 'agy prompt should put task context first');
145-
assert.ok(agyBranch.includes('${promptForArgs}\\n\\n---\\n\\n[Operational Context'), 'agy should append operational context after task prompt');
146-
assert.ok(
147-
agyBranch.indexOf('[Current cli-jaw task]') < agyBranch.indexOf('[Operational Context'),
148-
'agy must not put the long operational context before the current user task',
149-
);
144+
assert.ok(agyBranch.includes('composeAgyPrompt('), 'agy should use the dedicated prompt composer');
145+
assert.ok(agyBranch.includes('resolveAgyPromptOrder(cfg.promptOrder)'), 'agy should resolve the per-cli prompt order');
150146
});
151147

152148
// ─── SF-EDGE: processQueue is called after mainManaged exit ───

0 commit comments

Comments
 (0)