portable-agent-layer 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/assets/agents/gemini-researcher.md +73 -0
- package/assets/agents/grok-researcher.md +10 -1
- package/assets/agents/perplexity-researcher.md +67 -0
- package/assets/skills/analyze-youtube/SKILL.md +1 -1
- package/assets/skills/analyze-youtube/tools/youtube-analyze.ts +3 -3
- package/assets/skills/create-pdf/SKILL.md +53 -76
- package/assets/skills/fyzz-chat-api/SKILL.md +3 -3
- package/assets/skills/fyzz-chat-api/tools/fyzz-api.ts +4 -4
- package/assets/skills/research/SKILL.md +9 -9
- package/assets/skills/research/tools/gemini-search.ts +186 -0
- package/assets/skills/research/tools/grok-search.ts +3 -3
- package/assets/skills/research/tools/perplexity-search.ts +150 -0
- package/assets/templates/PAL/ALGORITHM.md +71 -9
- package/assets/templates/PAL/WORK_TRACKING.md +2 -9
- package/package.json +1 -1
- package/src/cli/index.ts +18 -6
- package/src/hooks/handlers/rating.ts +1 -1
- package/src/hooks/handlers/relationship.ts +2 -2
- package/src/hooks/handlers/session-name.ts +1 -1
- package/src/hooks/handlers/work-learning.ts +9 -0
- package/src/hooks/lib/claude-md.ts +17 -5
- package/src/hooks/lib/context.ts +35 -55
- package/src/hooks/lib/export.ts +3 -2
- package/src/hooks/lib/graduation.ts +1 -1
- package/src/hooks/lib/inference.ts +1 -1
- package/src/hooks/lib/paths.ts +1 -0
- package/src/hooks/lib/readme-sync.ts +6 -6
- package/src/hooks/lib/security.ts +5 -1
- package/src/hooks/lib/work-tracking.ts +29 -42
- package/src/targets/claude/install.ts +2 -0
- package/src/targets/cursor/install.ts +2 -0
- package/src/targets/lib.ts +93 -0
- package/src/targets/opencode/install.ts +2 -0
- package/src/tools/agent/algorithm-reflect.ts +120 -0
- package/src/tools/agent/wisdom-frame.ts +0 -2
- package/assets/agents/claude-researcher.md +0 -43
- package/assets/agents/investigative-researcher.md +0 -44
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Gemini Search — CLI tool for grounded search via the Gemini API.
|
|
4
|
+
*
|
|
5
|
+
* Uses Gemini's built-in Google Search grounding to fetch real-time,
|
|
6
|
+
* source-cited information. Optimized for academic and scholarly queries.
|
|
7
|
+
*
|
|
8
|
+
* Requires PAL_GEMINI_API_KEY environment variable.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* bun gemini-search.ts -- <query> [--max-tokens 4096]
|
|
12
|
+
* bun gemini-search.ts -- "recent advances in transformer architectures"
|
|
13
|
+
* bun gemini-search.ts -- "CRISPR gene editing clinical trials 2025"
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { parseArgs } from "node:util";
|
|
17
|
+
|
|
18
|
+
const API_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
19
|
+
const DEFAULT_MODEL = "gemini-3.1-flash-lite-preview";
|
|
20
|
+
|
|
21
|
+
interface GroundingChunk {
|
|
22
|
+
web?: { uri: string; title: string };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface GroundingSupport {
|
|
26
|
+
segment?: { startIndex: number; endIndex: number; text: string };
|
|
27
|
+
groundingChunkIndices?: number[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface GroundingMetadata {
|
|
31
|
+
webSearchQueries?: string[];
|
|
32
|
+
groundingChunks?: GroundingChunk[];
|
|
33
|
+
groundingSupports?: GroundingSupport[];
|
|
34
|
+
searchEntryPoint?: { renderedContent: string };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ContentPart {
|
|
38
|
+
text?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface Candidate {
|
|
42
|
+
content?: { parts?: ContentPart[]; role?: string };
|
|
43
|
+
groundingMetadata?: GroundingMetadata;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface GeminiResponse {
|
|
47
|
+
candidates?: Candidate[];
|
|
48
|
+
error?: { message: string; code: number };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function loadApiKey(): string {
|
|
52
|
+
const key = process.env.PAL_GEMINI_API_KEY;
|
|
53
|
+
if (!key) {
|
|
54
|
+
console.error("Error: PAL_GEMINI_API_KEY environment variable is not set.");
|
|
55
|
+
console.error("Get an API key at https://aistudio.google.com/apikey");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
return key;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const SYSTEM_PROMPT = `You are an academic research assistant. When searching, prioritize:
|
|
62
|
+
- Peer-reviewed papers, preprints (arXiv, bioRxiv, medRxiv)
|
|
63
|
+
- Official documentation and technical specifications
|
|
64
|
+
- University and research institution publications
|
|
65
|
+
- Conference proceedings (NeurIPS, ICML, ACL, CVPR, etc.)
|
|
66
|
+
- Systematic reviews and meta-analyses
|
|
67
|
+
|
|
68
|
+
Always include: author names, publication year, journal/venue when available.
|
|
69
|
+
Distinguish between peer-reviewed findings and preprints/working papers.
|
|
70
|
+
Note methodology limitations and sample sizes when relevant.
|
|
71
|
+
Be thorough but concise.`;
|
|
72
|
+
|
|
73
|
+
export async function geminiSearch(query: string, maxTokens: number): Promise<void> {
|
|
74
|
+
const apiKey = loadApiKey();
|
|
75
|
+
|
|
76
|
+
const body = {
|
|
77
|
+
system_instruction: {
|
|
78
|
+
parts: [{ text: SYSTEM_PROMPT }],
|
|
79
|
+
},
|
|
80
|
+
contents: [
|
|
81
|
+
{
|
|
82
|
+
parts: [{ text: query }],
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
tools: [{ google_search: {} }],
|
|
86
|
+
generationConfig: {
|
|
87
|
+
maxOutputTokens: maxTokens,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const url = `${API_BASE}/models/${DEFAULT_MODEL}:generateContent?key=${apiKey}`;
|
|
92
|
+
|
|
93
|
+
const response = await fetch(url, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "Content-Type": "application/json" },
|
|
96
|
+
body: JSON.stringify(body),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
const err = await response.text().catch(() => "");
|
|
101
|
+
console.error(`Error: HTTP ${response.status} — ${err.slice(0, 500)}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const data = (await response.json()) as GeminiResponse;
|
|
106
|
+
|
|
107
|
+
if (data.error) {
|
|
108
|
+
console.error(`Error: ${data.error.message}`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!data.candidates || data.candidates.length === 0) {
|
|
113
|
+
console.error("Error: No candidates in Gemini response.");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const candidate = data.candidates[0];
|
|
118
|
+
|
|
119
|
+
// Extract text
|
|
120
|
+
const textParts: string[] = [];
|
|
121
|
+
if (candidate.content?.parts) {
|
|
122
|
+
for (const part of candidate.content.parts) {
|
|
123
|
+
if (part.text) textParts.push(part.text);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (textParts.length === 0) {
|
|
128
|
+
console.error("Error: No text content in Gemini response.");
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(textParts.join("\n\n"));
|
|
133
|
+
|
|
134
|
+
// Extract grounding metadata
|
|
135
|
+
const meta = candidate.groundingMetadata;
|
|
136
|
+
if (meta) {
|
|
137
|
+
if (meta.webSearchQueries && meta.webSearchQueries.length > 0) {
|
|
138
|
+
console.log("\n---\n## Search Queries Used\n");
|
|
139
|
+
for (const q of meta.webSearchQueries) {
|
|
140
|
+
console.log(`- ${q}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (meta.groundingChunks && meta.groundingChunks.length > 0) {
|
|
145
|
+
console.log("\n---\n## Sources\n");
|
|
146
|
+
for (const chunk of meta.groundingChunks) {
|
|
147
|
+
if (chunk.web) {
|
|
148
|
+
console.log(`- [${chunk.web.title}](${chunk.web.uri})`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function run() {
|
|
156
|
+
const { positionals, values } = parseArgs({
|
|
157
|
+
allowPositionals: true,
|
|
158
|
+
options: {
|
|
159
|
+
"max-tokens": { type: "string", short: "m", default: "4096" },
|
|
160
|
+
help: { type: "boolean", short: "h" },
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (values.help || positionals.length === 0) {
|
|
165
|
+
console.log(`Gemini Search — grounded academic search via Gemini API
|
|
166
|
+
|
|
167
|
+
Usage:
|
|
168
|
+
bun gemini-search.ts -- <query> [options]
|
|
169
|
+
|
|
170
|
+
Options:
|
|
171
|
+
--max-tokens, -m <n> Max response tokens (default: 4096)
|
|
172
|
+
--help, -h Show this help
|
|
173
|
+
|
|
174
|
+
Examples:
|
|
175
|
+
bun gemini-search.ts -- "transformer architecture advances 2025"
|
|
176
|
+
bun gemini-search.ts -- "CRISPR clinical trials"`);
|
|
177
|
+
process.exit(0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const query = positionals.join(" ");
|
|
181
|
+
const maxTokens = Number.parseInt(values["max-tokens"] ?? "4096", 10);
|
|
182
|
+
|
|
183
|
+
await geminiSearch(query, maxTokens);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (import.meta.main) run();
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Uses the Grok Responses API with web_search and x_search tools
|
|
6
6
|
* to fetch real-time information from the web and X (Twitter).
|
|
7
7
|
*
|
|
8
|
-
* Requires
|
|
8
|
+
* Requires PAL_XAI_API_KEY environment variable.
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
11
11
|
* bun grok-search.ts -- <query> [--sources web,x] [--max-tokens 2048]
|
|
@@ -45,9 +45,9 @@ interface GrokResponse {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function loadApiKey(): string {
|
|
48
|
-
const key = process.env.
|
|
48
|
+
const key = process.env.PAL_XAI_API_KEY;
|
|
49
49
|
if (!key) {
|
|
50
|
-
console.error("Error:
|
|
50
|
+
console.error("Error: PAL_XAI_API_KEY environment variable is not set.");
|
|
51
51
|
console.error("Get an API key at https://console.x.ai/");
|
|
52
52
|
process.exit(1);
|
|
53
53
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Perplexity Search — CLI tool for investigative search via the Perplexity Sonar API.
|
|
4
|
+
*
|
|
5
|
+
* Uses Perplexity's Sonar model with built-in web search to fetch
|
|
6
|
+
* source-cited, verified information. Optimized for investigative queries
|
|
7
|
+
* requiring cross-referenced, credible sources.
|
|
8
|
+
*
|
|
9
|
+
* Requires PAL_PERPLEXITY_API_KEY environment variable.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* bun perplexity-search.ts -- <query> [--max-tokens 4096]
|
|
13
|
+
* bun perplexity-search.ts -- "corruption allegations against company X"
|
|
14
|
+
* bun perplexity-search.ts -- "timeline of event Y with sources"
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { parseArgs } from "node:util";
|
|
18
|
+
|
|
19
|
+
const API_BASE = "https://api.perplexity.ai";
|
|
20
|
+
const DEFAULT_MODEL = "sonar-pro";
|
|
21
|
+
|
|
22
|
+
interface Choice {
|
|
23
|
+
message?: {
|
|
24
|
+
role: string;
|
|
25
|
+
content: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface PerplexityResponse {
|
|
30
|
+
choices?: Choice[];
|
|
31
|
+
citations?: string[];
|
|
32
|
+
error?: { message: string; code?: number };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function loadApiKey(): string {
|
|
36
|
+
const key = process.env.PAL_PERPLEXITY_API_KEY;
|
|
37
|
+
if (!key) {
|
|
38
|
+
console.error(
|
|
39
|
+
"Error: PAL_PERPLEXITY_API_KEY environment variable is not set.\n" +
|
|
40
|
+
"The Perplexity API could not be reached. The researcher agent should fall back to WebSearch.\n" +
|
|
41
|
+
"To enable Perplexity search, get an API key at https://www.perplexity.ai/settings/api\n" +
|
|
42
|
+
"and set it: export PAL_PERPLEXITY_API_KEY=pplx-..."
|
|
43
|
+
);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
return key;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const SYSTEM_PROMPT = `You are an investigative research assistant. When searching, prioritize:
|
|
50
|
+
- Cross-referenced facts verified by 2+ independent sources
|
|
51
|
+
- Primary sources: court filings, official reports, government records, regulatory filings
|
|
52
|
+
- Credible journalism: established outlets with editorial standards
|
|
53
|
+
- Source credibility assessment: note publication reputation, potential bias, date of publication
|
|
54
|
+
- Evidence chains: connect claims to their original sources
|
|
55
|
+
|
|
56
|
+
Always include: source names, publication dates, and direct quotes when available.
|
|
57
|
+
Distinguish between confirmed facts, single-source claims, and unverified allegations.
|
|
58
|
+
Flag contradictions between sources.
|
|
59
|
+
Be thorough but concise.`;
|
|
60
|
+
|
|
61
|
+
export async function perplexitySearch(query: string, maxTokens: number): Promise<void> {
|
|
62
|
+
const apiKey = loadApiKey();
|
|
63
|
+
|
|
64
|
+
const body = {
|
|
65
|
+
model: DEFAULT_MODEL,
|
|
66
|
+
messages: [
|
|
67
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
68
|
+
{ role: "user", content: query },
|
|
69
|
+
],
|
|
70
|
+
max_tokens: maxTokens,
|
|
71
|
+
return_citations: true,
|
|
72
|
+
search_recency_filter: "week",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const response = await fetch(`${API_BASE}/chat/completions`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${apiKey}`,
|
|
79
|
+
"Content-Type": "application/json",
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify(body),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
const err = await response.text().catch(() => "");
|
|
86
|
+
console.error(`Error: HTTP ${response.status} — ${err.slice(0, 500)}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const data = (await response.json()) as PerplexityResponse;
|
|
91
|
+
|
|
92
|
+
if (data.error) {
|
|
93
|
+
console.error(`Error: ${data.error.message}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!data.choices || data.choices.length === 0) {
|
|
98
|
+
console.error("Error: No choices in Perplexity response.");
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const content = data.choices[0].message?.content;
|
|
103
|
+
if (!content) {
|
|
104
|
+
console.error("Error: No text content in Perplexity response.");
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(content);
|
|
109
|
+
|
|
110
|
+
// Extract citations
|
|
111
|
+
if (data.citations && data.citations.length > 0) {
|
|
112
|
+
console.log("\n---\n## Sources\n");
|
|
113
|
+
for (let i = 0; i < data.citations.length; i++) {
|
|
114
|
+
console.log(`- [${i + 1}] ${data.citations[i]}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function run() {
|
|
120
|
+
const { positionals, values } = parseArgs({
|
|
121
|
+
allowPositionals: true,
|
|
122
|
+
options: {
|
|
123
|
+
"max-tokens": { type: "string", short: "m", default: "4096" },
|
|
124
|
+
help: { type: "boolean", short: "h" },
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (values.help || positionals.length === 0) {
|
|
129
|
+
console.log(`Perplexity Search — investigative search via Perplexity Sonar API
|
|
130
|
+
|
|
131
|
+
Usage:
|
|
132
|
+
bun perplexity-search.ts -- <query> [options]
|
|
133
|
+
|
|
134
|
+
Options:
|
|
135
|
+
--max-tokens, -m <n> Max response tokens (default: 4096)
|
|
136
|
+
--help, -h Show this help
|
|
137
|
+
|
|
138
|
+
Examples:
|
|
139
|
+
bun perplexity-search.ts -- "corruption allegations timeline"
|
|
140
|
+
bun perplexity-search.ts -- "regulatory actions against company X"`);
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const query = positionals.join(" ");
|
|
145
|
+
const maxTokens = Number.parseInt(values["max-tokens"] ?? "4096", 10);
|
|
146
|
+
|
|
147
|
+
await perplexitySearch(query, maxTokens);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (import.meta.main) run();
|
|
@@ -35,13 +35,55 @@ Format:
|
|
|
35
35
|
|
|
36
36
|
Include at least one anti-criterion (C-A prefix).
|
|
37
37
|
|
|
38
|
-
**3.
|
|
38
|
+
**3. Capability audit:**
|
|
39
39
|
|
|
40
|
-
Scan
|
|
40
|
+
Scan ALL 14 capabilities below. For each, assign exactly one disposition:
|
|
41
|
+
- **USE** — will invoke during a specific phase. State which.
|
|
42
|
+
- **DECLINE** — would help but not worth it for this task's scope.
|
|
43
|
+
- **N/A** — genuinely irrelevant to this task.
|
|
44
|
+
|
|
45
|
+
**A: Foundation**
|
|
46
|
+
|
|
47
|
+
| # | Capability | Invocation |
|
|
48
|
+
|---|-----------|------------|
|
|
49
|
+
| 1 | Task Tool | TaskCreate, TaskUpdate, TaskList |
|
|
50
|
+
| 2 | AskUserQuestion | Built-in tool |
|
|
51
|
+
| 3 | Skills (ACTIVE SCAN) | Read `skill-index.json`, match triggers against task |
|
|
52
|
+
|
|
53
|
+
**B: Thinking & Analysis**
|
|
54
|
+
|
|
55
|
+
| # | Capability | Invocation |
|
|
56
|
+
|---|-----------|------------|
|
|
57
|
+
| 4 | Think (analysis router) | `think` skill |
|
|
58
|
+
| 5 | First Principles | `first-principles` skill |
|
|
59
|
+
| 6 | Council (multi-perspective) | `council` skill |
|
|
60
|
+
| 7 | Plan Mode | EnterPlanMode tool |
|
|
61
|
+
|
|
62
|
+
**C: Agents & Research**
|
|
63
|
+
|
|
64
|
+
| # | Capability | Invocation |
|
|
65
|
+
|---|-----------|------------|
|
|
66
|
+
| 8 | Research (multi-agent) | `research` skill |
|
|
67
|
+
| 9 | Subagents | Agent tool (Explore, Plan, general-purpose) |
|
|
68
|
+
| 10 | Background agents | Agent tool with `run_in_background: true` |
|
|
69
|
+
|
|
70
|
+
**D: Execution & Verification**
|
|
71
|
+
|
|
72
|
+
| # | Capability | Invocation |
|
|
73
|
+
|---|-----------|------------|
|
|
74
|
+
| 11 | Git worktree isolation | `isolation: "worktree"` on Agent |
|
|
75
|
+
| 12 | Test runner | `bun test`, vitest, jest, pytest |
|
|
76
|
+
| 13 | Static analysis | `tsc --noEmit`, biome, eslint |
|
|
77
|
+
| 14 | CLI probes | curl, diff, jq, exit codes |
|
|
78
|
+
|
|
79
|
+
**Capability #3 (Skills) requires active scanning.** Read `skill-index.json` and match the task against skill triggers. "Skills — N/A" without evidence of scanning is an error.
|
|
41
80
|
|
|
42
81
|
Output:
|
|
43
82
|
```
|
|
44
|
-
🏹 CAPABILITIES
|
|
83
|
+
🏹 CAPABILITIES (14/14):
|
|
84
|
+
USE: [#, #, #] — [reason (phase: WHICH)]
|
|
85
|
+
DECLINE: [#, #] — [reason]
|
|
86
|
+
N/A: [rest]
|
|
45
87
|
```
|
|
46
88
|
|
|
47
89
|
### ━━━ 🧠 PLAN ━━━ 2/5
|
|
@@ -92,11 +134,25 @@ If any criteria failed, fix and re-verify before completing.
|
|
|
92
134
|
|
|
93
135
|
Reflect on the work and capture reusable knowledge. Skip this phase when the work was trivial or purely mechanical.
|
|
94
136
|
|
|
95
|
-
**1. Reflection** (one sentence each):
|
|
96
|
-
|
|
97
|
-
|
|
137
|
+
**1. Algorithm Reflection** (one sentence each — reflect on ALGORITHM PERFORMANCE, not task subject matter):
|
|
138
|
+
|
|
139
|
+
**Q1 — Self:** "What would I have done differently in this Algorithm run?"
|
|
140
|
+
Focus: phase execution, criteria quality, capability selection decisions.
|
|
141
|
+
|
|
142
|
+
**Q2 — Algorithm:** "What would a smarter algorithm have done differently?"
|
|
143
|
+
Focus: structural improvements — missing phases, better gating, capability triggers, ISC patterns.
|
|
144
|
+
|
|
145
|
+
**Q3 — AI:** "What would a fundamentally smarter AI have done differently?"
|
|
146
|
+
Focus: reasoning approach, problem decomposition, anticipation, blind spots.
|
|
147
|
+
|
|
148
|
+
**2. Reflection Log** — record algorithm performance:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
bun ~/.agents/PAL/tools/algorithm-reflect.ts --task "description" --criteria N --passed N --failed N --sentiment 1-10 \
|
|
152
|
+
--q1 "self reflection" --q2 "algorithm reflection" --q3 "AI reflection"
|
|
153
|
+
```
|
|
98
154
|
|
|
99
|
-
**
|
|
155
|
+
**3. Wisdom Frame** — if the session produced a genuine, reusable insight:
|
|
100
156
|
|
|
101
157
|
```bash
|
|
102
158
|
bun ~/.agents/PAL/tools/wisdom-frame.ts --domain <domain> --observation "insight" [--type principle|contextual-rule|anti-pattern|evolution]
|
|
@@ -118,7 +174,10 @@ Only write if the insight is **genuine and reusable** — not every session prod
|
|
|
118
174
|
📋 CRITERIA:
|
|
119
175
|
[criteria checklist]
|
|
120
176
|
|
|
121
|
-
🏹 CAPABILITIES:
|
|
177
|
+
🏹 CAPABILITIES (14/14):
|
|
178
|
+
USE: [#, #] — [reason]
|
|
179
|
+
DECLINE: [#] — [reason]
|
|
180
|
+
N/A: [rest]
|
|
122
181
|
|
|
123
182
|
━━━ 🧠 PLAN ━━━ 2/5
|
|
124
183
|
🧠 RISKS: [risks]
|
|
@@ -136,6 +195,9 @@ Only write if the insight is **genuine and reusable** — not every session prod
|
|
|
136
195
|
🗣️ {{IDENTITY_NAME}}: [summary]
|
|
137
196
|
|
|
138
197
|
━━━ 📚 LEARN ━━━ 5/5
|
|
139
|
-
🪞
|
|
198
|
+
🪞 Q1 — Self: [what I'd do differently]
|
|
199
|
+
🪞 Q2 — Algorithm: [structural improvement]
|
|
200
|
+
🪞 Q3 — AI: [reasoning blind spot]
|
|
201
|
+
📊 REFLECTION LOG: [appended to algorithm-reflections.jsonl]
|
|
140
202
|
📝 WISDOM: [frame update if genuine insight, or "No new insight"]
|
|
141
203
|
```
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
# Work Tracking
|
|
2
2
|
|
|
3
|
-
PAL tracks your work across sessions in `memory/state/sessions.json` (auto-captured)
|
|
3
|
+
PAL tracks your work across sessions in `memory/state/sessions.json` (auto-captured).
|
|
4
4
|
|
|
5
5
|
## Projects
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **Starting sustained multi-session work** → create a project with objectives and an id (slugified, e.g. "pdf-template-engine")
|
|
9
|
-
- **Making a key decision** → add to the project's `decisions` array
|
|
10
|
-
- **Completing a milestone** → add to `completed`, remove from `nextSteps`
|
|
11
|
-
- **Session ends with open work** → update `nextSteps` and `handoff`
|
|
12
|
-
- **Work is done** → set status to "completed"
|
|
13
|
-
|
|
14
|
-
Do not create projects for one-off questions or quick fixes.
|
|
7
|
+
Projects are managed in `telos/PROJECTS.md` and force-loaded at session startup via `pal-settings.json → loadAtStartup.files`.
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -377,12 +377,12 @@ function doctor(silent = false): DoctorResult {
|
|
|
377
377
|
telosCount > 0 ? ok(`TELOS: ${telosCount} files`) : fail("TELOS: not scaffolded");
|
|
378
378
|
|
|
379
379
|
// API key checks
|
|
380
|
-
process.env.
|
|
381
|
-
? ok("
|
|
382
|
-
: fail("
|
|
383
|
-
process.env.
|
|
384
|
-
? ok("
|
|
385
|
-
: warn("
|
|
380
|
+
process.env.PAL_ANTHROPIC_API_KEY
|
|
381
|
+
? ok("PAL_ANTHROPIC_API_KEY is set")
|
|
382
|
+
: fail("PAL_ANTHROPIC_API_KEY — not set (hooks need it for inference)");
|
|
383
|
+
process.env.PAL_GEMINI_API_KEY
|
|
384
|
+
? ok("PAL_GEMINI_API_KEY is set")
|
|
385
|
+
: warn("PAL_GEMINI_API_KEY — not set (optional, for YouTube analysis)");
|
|
386
386
|
|
|
387
387
|
// Hook health from debug.log
|
|
388
388
|
const hookHealth = checkHookHealth(home);
|
|
@@ -443,6 +443,18 @@ async function init(args: string[]) {
|
|
|
443
443
|
}
|
|
444
444
|
|
|
445
445
|
async function install(targets: { claude: boolean; opencode: boolean; cursor: boolean }) {
|
|
446
|
+
// Ensure dependencies are installed
|
|
447
|
+
const pkg = palPkg();
|
|
448
|
+
log.info("Installing dependencies...");
|
|
449
|
+
const deps = spawnSync("bun", ["install", "--frozen-lockfile"], {
|
|
450
|
+
cwd: pkg,
|
|
451
|
+
stdio: "inherit",
|
|
452
|
+
shell: true,
|
|
453
|
+
});
|
|
454
|
+
if (deps.status !== 0) {
|
|
455
|
+
log.warn("bun install failed — continuing anyway, but hooks may not work");
|
|
456
|
+
}
|
|
457
|
+
|
|
446
458
|
// Scaffold TELOS + PAL settings, then prompt for missing identity
|
|
447
459
|
const { scaffoldTelos, scaffoldPalSettings } = await import("../targets/lib");
|
|
448
460
|
const { promptIdentity } = await import("./setup-identity");
|
|
@@ -358,6 +358,6 @@ export async function captureRating(message: string, sessionId?: string): Promis
|
|
|
358
358
|
return;
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
-
// Path 2: Implicit sentiment (requires
|
|
361
|
+
// Path 2: Implicit sentiment (requires PAL_ANTHROPIC_API_KEY — inference silently no-ops without it)
|
|
362
362
|
await handleImplicitSentiment(cleaned, sessionId);
|
|
363
363
|
}
|
|
@@ -52,8 +52,8 @@ export async function captureRelationship(
|
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
if (!process.env.
|
|
56
|
-
logDebug("relationship", "Skipped: no
|
|
55
|
+
if (!process.env.PAL_ANTHROPIC_API_KEY) {
|
|
56
|
+
logDebug("relationship", "Skipped: no PAL_ANTHROPIC_API_KEY");
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -42,7 +42,7 @@ export async function captureSessionName(
|
|
|
42
42
|
logDebug("session-name", `Named from prompt: "${name}"`);
|
|
43
43
|
|
|
44
44
|
// Spawn detached background process to upgrade with Haiku inference
|
|
45
|
-
if (!process.env.
|
|
45
|
+
if (!process.env.PAL_ANTHROPIC_API_KEY) return;
|
|
46
46
|
try {
|
|
47
47
|
const promptB64 = Buffer.from(message.slice(0, 800)).toString("base64");
|
|
48
48
|
const child = spawn(
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
extractLastUser,
|
|
20
20
|
parseMessages,
|
|
21
21
|
} from "../lib/transcript";
|
|
22
|
+
import { appendProjectHistory } from "../lib/work-tracking";
|
|
22
23
|
|
|
23
24
|
function slugify(text: string): string {
|
|
24
25
|
return text
|
|
@@ -183,5 +184,13 @@ export async function captureWorkLearning(
|
|
|
183
184
|
const filepath = resolve(dir, filename);
|
|
184
185
|
writeFileSync(filepath, content, "utf-8");
|
|
185
186
|
|
|
187
|
+
// Append to per-project history (agent-agnostic recall)
|
|
188
|
+
appendProjectHistory(process.cwd(), {
|
|
189
|
+
date: new Date().toISOString().slice(0, 10),
|
|
190
|
+
title,
|
|
191
|
+
summary,
|
|
192
|
+
insights,
|
|
193
|
+
});
|
|
194
|
+
|
|
186
195
|
if (sessionId) markCaptured(sessionId, filepath, messages.length);
|
|
187
196
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
+
copyFileSync,
|
|
10
11
|
existsSync,
|
|
11
12
|
lstatSync,
|
|
12
13
|
readdirSync,
|
|
@@ -48,7 +49,7 @@ function latestMtime(...filePaths: string[]): number {
|
|
|
48
49
|
return latest;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
/** Create or verify a symlink pointing to AGENTS.md */
|
|
52
|
+
/** Create or verify a symlink pointing to AGENTS.md (falls back to copy on Windows EPERM) */
|
|
52
53
|
function ensureOneSymlink(linkPath: string, targetPath: string): void {
|
|
53
54
|
try {
|
|
54
55
|
const stat = lstatSync(linkPath);
|
|
@@ -58,8 +59,16 @@ function ensureOneSymlink(linkPath: string, targetPath: string): void {
|
|
|
58
59
|
// doesn't exist — create it
|
|
59
60
|
}
|
|
60
61
|
ensureDir(dirname(linkPath));
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
try {
|
|
63
|
+
const relTarget = relative(dirname(linkPath), targetPath).replaceAll("\\", "/");
|
|
64
|
+
symlinkSync(relTarget, linkPath);
|
|
65
|
+
} catch (e: unknown) {
|
|
66
|
+
if ((e as NodeJS.ErrnoException).code === "EPERM") {
|
|
67
|
+
copyFileSync(targetPath, linkPath);
|
|
68
|
+
} else {
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
/** Ensure all agent symlinks point to the canonical AGENTS.md */
|
|
@@ -156,9 +165,12 @@ export function buildClaudeMd(): string {
|
|
|
156
165
|
/** Regenerate AGENTS.md if any source file is newer, and ensure CLAUDE.md symlink exists. Returns true if rebuilt. */
|
|
157
166
|
export function regenerateIfNeeded(): boolean {
|
|
158
167
|
const { outputPath } = getOutputPaths();
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
if (!needsRebuild()) {
|
|
169
|
+
ensureSymlinks();
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
161
172
|
ensureDir(dirname(outputPath));
|
|
162
173
|
writeFileSync(outputPath, buildClaudeMd(), "utf-8");
|
|
174
|
+
ensureSymlinks();
|
|
163
175
|
return true;
|
|
164
176
|
}
|