feat(headless): accept sender id via --from-agent

This commit is contained in:
cpacker
2026-01-31 14:45:16 -08:00
parent 375e485874
commit f6977b043d
3 changed files with 52 additions and 15 deletions

View File

@@ -41,6 +41,7 @@ import { formatErrorDetails } from "./cli/helpers/errorFormatter";
import { safeJsonParseOr } from "./cli/helpers/safeJsonParse";
import { drainStreamWithResume } from "./cli/helpers/stream";
import { StreamProcessor } from "./cli/helpers/streamProcessor";
import { SYSTEM_REMINDER_CLOSE, SYSTEM_REMINDER_OPEN } from "./constants";
import { settingsManager } from "./settings-manager";
import { checkToolPermission } from "./tools/manager";
import type {
@@ -105,6 +106,7 @@ export async function handleHeadlessCommand(
"output-format": { type: "string" },
"input-format": { type: "string" },
"include-partial-messages": { type: "boolean" },
"from-agent": { type: "string" },
// Additional flags from index.ts that need to be filtered out
help: { type: "boolean", short: "h" },
version: { type: "boolean", short: "v" },
@@ -209,7 +211,8 @@ export async function handleHeadlessCommand(
}
// --new: Create a new conversation (for concurrent sessions)
const forceNewConversation = (values.new as boolean | undefined) ?? false;
let forceNewConversation = (values.new as boolean | undefined) ?? false;
const fromAgentId = values["from-agent"] as string | undefined;
// Resolve agent (same logic as interactive mode)
let agent: AgentState | null = null;
@@ -262,6 +265,26 @@ export async function handleHeadlessCommand(
process.exit(1);
}
if (fromAgentId) {
if (!specifiedAgentId && !specifiedConversationId) {
console.error(
"Error: --from-agent requires --agent <id> or --conversation <id>.",
);
process.exit(1);
}
if (shouldContinue) {
console.error("Error: --from-agent cannot be used with --continue");
process.exit(1);
}
if (forceNew) {
console.error("Error: --from-agent cannot be used with --new-agent");
process.exit(1);
}
if (!specifiedConversationId && !forceNewConversation) {
forceNewConversation = true;
}
}
// Validate --conversation flag (mutually exclusive with agent-selection flags)
// Exception: --conv default requires --agent
if (specifiedConversationId && specifiedConversationId !== "default") {
@@ -988,6 +1011,19 @@ export async function handleHeadlessCommand(
const { hasLoadedSkills } = await import("./agent/context");
let messageContent = "";
if (fromAgentId) {
const senderAgentId = fromAgentId;
const senderAgent = await client.agents.retrieve(senderAgentId);
const systemReminder = `${SYSTEM_REMINDER_OPEN}
This message is from "${senderAgent.name}" (agent ID: ${senderAgentId}), an agent currently running inside the Letta Code CLI (docs.letta.com/letta-code).
The sender will only see the final message you generate (not tool calls or reasoning).
If you need to share detailed information, include it in your response text.
${SYSTEM_REMINDER_CLOSE}
`;
messageContent += systemReminder;
}
// Add plan mode reminder if in plan mode (highest priority)
if (permissionMode.getMode() === "plan") {
const { PLAN_MODE_REMINDER } = await import("./agent/promptAssets");

View File

@@ -76,6 +76,7 @@ OPTIONS
When set, reads JSON messages from stdin for bidirectional communication
--include-partial-messages
Emit stream_event wrappers for each chunk (stream-json only)
--from-agent <id> Inject agent-to-agent system reminder (headless mode)
--skills <path> Custom path to skills directory (default: .skills in current directory)
--sleeptime Enable sleeptime memory management (only for new agents)
--from-af <path> Create agent from an AgentFile (.af) template
@@ -435,6 +436,7 @@ async function main(): Promise<void> {
"output-format": { type: "string" },
"input-format": { type: "string" },
"include-partial-messages": { type: "boolean" },
"from-agent": { type: "string" },
skills: { type: "string" },
sleeptime: { type: "boolean" },
"from-af": { type: "string" },

View File

@@ -58,27 +58,26 @@ letta messages search --query "topic" --all-agents
```
Results include `agent_id` for each matching message.
## CLI Usage
## CLI Usage (agent-to-agent)
### Starting a New Conversation
```bash
letta messages start-conversation --agent <id> --message "<text>"
letta -p --from-agent $LETTA_AGENT_ID --agent <id> "message text"
```
**Arguments:**
| Arg | Required | Description |
|-----|----------|-------------|
| `--agent <id>` | Yes | Target agent ID to message |
| `--agent-id <id>` | No | Alias for `--agent` |
| `--message <text>` | Yes | Message to send |
| `--timeout <ms>` | No | Max wait time in ms (default: 120000) |
| `--from-agent <id>` | Yes | Sender agent ID (injects agent-to-agent system reminder) |
| `"message text"` | Yes | Message body (positional after flags) |
**Example:**
```bash
letta messages start-conversation \
letta -p --from-agent $LETTA_AGENT_ID \
--agent agent-abc123 \
--message "What do you know about the authentication system?"
"What do you know about the authentication system?"
```
**Response:**
@@ -94,21 +93,21 @@ letta messages start-conversation \
### Continuing a Conversation
```bash
letta messages continue-conversation --conversation-id <id> --message "<text>"
letta -p --from-agent $LETTA_AGENT_ID --conversation <id> "message text"
```
**Arguments:**
| Arg | Required | Description |
|-----|----------|-------------|
| `--conversation-id <id>` | Yes | Existing conversation ID |
| `--message <text>` | Yes | Follow-up message to send |
| `--timeout <ms>` | No | Max wait time in ms (default: 120000) |
| `--conversation <id>` | Yes | Existing conversation ID |
| `--from-agent <id>` | Yes | Sender agent ID (injects agent-to-agent system reminder) |
| `"message text"` | Yes | Follow-up message (positional after flags) |
**Example:**
```bash
letta messages continue-conversation \
--conversation-id conversation-xyz789 \
--message "Can you explain more about the token refresh flow?"
letta -p --from-agent $LETTA_AGENT_ID \
--conversation conversation-xyz789 \
"Can you explain more about the token refresh flow?"
```
## Understanding the Response