wire conscience, fix subagent model resolution, clean up create.ts

- headless: reflection + transcript in bidirectional; conscience env vars route to persistent agent (falls back clean)
- manager: prefer llm_config.handle directly — stops the 500 on self-hosted
- create: optionstags typo fixed, lettabot tag exclusion added

Aster is persistent now. She has a conscience.
This commit is contained in:
Ani Tunturi
2026-03-26 09:09:37 -04:00
committed by Ani
parent 2a14e315e1
commit 328532d184
7 changed files with 37 additions and 1676 deletions

1
.gitignore vendored
View File

@@ -157,3 +157,4 @@ dist
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
__pycache__/
src/models.json

View File

@@ -335,13 +335,20 @@ export async function createAgent(
// - memory_blocks: new blocks to create inline
// - block_ids: references to existing blocks (for shared memory)
const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
const tags = ["origin:letta-code"];
if (isSubagent) {
tags.push("role:subagent");
}
// Start with empty array, add user's tags first
let tags: string[] = [];
if (options.tags && Array.isArray(options.tags)) {
tags.push(...options.tags);
}
// Only add origin:letta-code if the agent is NOT origin:lettabot
// This prevents the dual-identity problem where agents see both prompts
if (!tags.includes("origin:lettabot") && !tags.includes("origin:letta-code")) {
tags.push("origin:letta-code");
}
if (isSubagent) {
tags.push("role:subagent");
}
const agentDescription =
options.description ?? `Letta Code agent created in ${process.cwd()}`;

View File

@@ -70,8 +70,10 @@ interface ExecutionState {
* Fetches from API and resolves to a known model ID
*/
function getModelHandleFromAgent(agent: {
llm_config?: { model_endpoint_type?: string | null; model?: string | null };
llm_config?: { handle?: string | null; model_endpoint_type?: string | null; model?: string | null };
}): string | null {
const handle = agent.llm_config?.handle;
if (handle) return handle;
const endpoint = agent.llm_config?.model_endpoint_type;
const model = agent.llm_config?.model;
if (endpoint && model) {

View File

@@ -2466,12 +2466,12 @@ async function runBidirectionalMode(
const {
buildAutoReflectionPayload,
finalizeAutoReflectionPayload,
buildParentMemorySnapshot,
buildReflectionSubagentPrompt,
} = await import("./cli/helpers/reflectionTranscript");
const { getMemoryFilesystemRoot } = await import(
"./agent/memoryFilesystem"
);
const { recompileAgentSystemPrompt } = await import("./agent/modify");
const autoPayload = await buildAutoReflectionPayload(
agent.id,
@@ -2486,7 +2486,16 @@ async function runBidirectionalMode(
}
const memoryDir = getMemoryFilesystemRoot(agent.id);
const parentMemory = await buildParentMemorySnapshot(memoryDir);
let parentMemory: string | undefined;
try {
parentMemory = await recompileAgentSystemPrompt(
conversationId,
agent.id,
true,
);
} catch {
debugWarn("memory", "Failed to fetch parent system prompt for reflection; proceeding without it");
}
const reflectionPrompt = buildReflectionSubagentPrompt({
transcriptPath: autoPayload.payloadPath,
memoryDir,
@@ -2495,11 +2504,20 @@ async function runBidirectionalMode(
});
const { spawnBackgroundSubagentTask } = await import("./tools/impl/Task");
// conscience: persistent supervisory agent (opt-in via env vars).
// Falls back to default ephemeral reflection if not configured.
const conscienceConversationId = process.env.CONSCIENCE_CONVERSATION_ID;
const conscienceAgentId = process.env.CONSCIENCE_AGENT_ID;
spawnBackgroundSubagentTask({
subagentType: "reflection",
prompt: reflectionPrompt,
description: "Reflect on recent conversations",
silentCompletion: true,
...(conscienceConversationId
? { existingConversationId: conscienceConversationId }
: conscienceAgentId
? { existingAgentId: conscienceAgentId }
: {}),
onComplete: async ({ success, error }) => {
await finalizeAutoReflectionPayload(
agent.id,

View File

@@ -1875,7 +1875,7 @@ async function main(): Promise<void> {
// so their prompts are left untouched by auto-heal.
if (
!storedPreset &&
agent.tags?.includes("origin:letta-code") &&
(agent.tags?.includes("origin:letta-code") || agent.tags?.includes("origin:lettabot")) &&
!agent.tags?.includes("role:subagent")
) {
storedPreset = "custom";

File diff suppressed because it is too large Load Diff

View File

@@ -226,7 +226,7 @@ export function shouldClearPersistedToolRules(
agent: AgentWithToolsAndRules,
): boolean {
return (
agent.tags?.includes("origin:letta-code") === true &&
(agent.tags?.includes("origin:letta-code") || agent.tags?.includes("origin:lettabot")) === true &&
(agent.tool_rules?.length ?? 0) > 0
);
}