Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,54 @@ field works without upgrading the CLI. `--from-version latest|N` starts from an
existing version and applies your overrides on top, replacing whole top-level
keys rather than deep-merging.

### Talking to an agent (A2A)

Every agent bound to a workspace answers over the
[A2A protocol](https://a2a-protocol.org). The CLI speaks A2A v1.0 over
HTTP+JSON; `--workspace` defaults to the remembered one, as for `agent bind`.

```bash
memorylake agent card <agent-id> [--workspace <id>]

memorylake agent send <agent-id> --text "..." [--text "..."] \
[--context CTX] [--task TASK] [--stream | --no-wait] [--raw] \
[--actor ID] [--project ID] [--read-only-project ID]... [--skip-memory] \
[--metadata-json '{"overrides":{...}}'] [--workspace <id>]
memorylake agent send <agent-id> --message-json '[{"text":"..."},{"url":"...","mediaType":"image/png"}]'
memorylake agent send <agent-id> --message-file parts.json

memorylake agent task list <agent-id> [--context CTX] [--status STATE] \
[--page-size N] [--page-token TOK] [--after TIMESTAMP] [--history-length N] [--artifacts]
memorylake agent task get <agent-id> <task-id> [--history-length N]
memorylake agent task cancel <agent-id> <task-id>
memorylake agent task feedback <agent-id> <task-id> --rating up|down [--comment TEXT]
```

`send` waits for the answer and prints the reply text on stdout; the task id,
context id and final state go to stderr, so a script can pipe the reply and
still know how to continue:

```
$ memorylake agent send agent-… --text "Summarize yesterday's standup"
The team agreed to …
task run-… context 5fdb… state TASK_STATE_COMPLETED
```

Pass `--context` to keep talking in the same thread. A task that ends in
`TASK_STATE_INPUT_REQUIRED` needs more from you: reply with `--task <id>
--context <id>`. `--stream` prints the reply as it is produced; `--no-wait`
returns the task as soon as it exists (poll it with `agent task get`); `--raw`
prints the protocol response as JSON instead of the reply text.

The `--actor`, `--project`, `--read-only-project` and `--skip-memory` flags set
MemoryLake's extension of the request (`metadata.memorylake`): whose message it
is, which projects the agent may read and write, and whether the exchange is
remembered at all. Anything else the extension accepts, such as `overrides`,
goes through `--metadata-json`.

Feedback is a MemoryLake extension to A2A. `task feedback` records a rating on
the task; `task get` reads it back under `metadata."task-feedback/v1"`.

### Search

```bash
Expand Down
41 changes: 40 additions & 1 deletion crates/cli/src/commands/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
//!
//! Agent *identity* (name, description, metadata) changes in place via
//! `agent update`. Agent *configuration* (model, policies, prompt, …) is
//! immutable and changes only by creating a new version.
//! immutable and changes only by creating a new version. Talking to a bound
//! agent (`card`, `send`, `task`) goes over A2A and lives in [`a2a`].

mod a2a;
mod body;

use anyhow::{Context, Result};
Expand All @@ -17,6 +19,7 @@ use memorylake_core::{Client, Paths, ResolveOverrides, resolve};
use std::path::PathBuf;

use super::require_workspace;
use a2a::{SendArgs, TaskCommand, run_card, run_send, run_task, task_workspace_flag};
use body::{FromVersion, load_config_body, reject_config_fields, require_field, set_scalar};

/// Agent subcommands.
Expand Down Expand Up @@ -138,6 +141,26 @@ pub enum AgentCommand {
#[arg(long = "name")]
name_fuzzy: Option<String>,
},
/// Show an agent's A2A card: capabilities, protocol bindings, extensions.
Card {
/// Agent id (must be bound to the workspace).
agent_id: String,
/// Workspace the agent is bound in.
///
/// Defaults to the workspace remembered by `workspace use`.
#[arg(long)]
workspace: Option<String>,
},
/// Send a message to an agent and print its reply.
///
/// Waits for the answer by default. The reply text goes to stdout; the
/// task and context ids needed to continue go to stderr.
Send(SendArgs),
/// Inspect, cancel and rate the tasks an agent has run.
Task {
#[command(subcommand)]
command: TaskCommand,
},
}

/// `agent version` subcommands.
Expand Down Expand Up @@ -303,6 +326,22 @@ pub fn run(command: AgentCommand, profile: Option<String>, base_url: Option<Stri
.with_context(|| format!("list agents bound to workspace `{workspace}`"))?;
println!("{}", serde_json::to_string_pretty(&data)?);
}
AgentCommand::Card {
agent_id,
workspace,
} => {
let workspace = require_workspace(&paths, &runtime.profile, workspace)?;
run_card(&client, &workspace, &agent_id)?;
}
AgentCommand::Send(args) => {
let workspace = require_workspace(&paths, &runtime.profile, args.workspace.clone())?;
run_send(&client, &workspace, args)?;
}
AgentCommand::Task { command } => {
let workspace =
require_workspace(&paths, &runtime.profile, task_workspace_flag(&command))?;
run_task(&client, &workspace, command)?;
}
}

Ok(())
Expand Down
Loading
Loading