# `Nous.Tool.ContextUpdate`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/tool/context_update.ex#L1)

Structured context updates from tools.

When tools need to update the agent's context (e.g., storing data for later use),
they can return a ContextUpdate along with their result. This provides a clear,
explicit way to modify context state without magic keys.

## Example

    defmodule MyTools do
      alias Nous.Tool.ContextUpdate

      def add_todo(ctx, %{"text" => text}) do
        todo = %{id: generate_id(), text: text, done: false}
        todos = [todo | ctx.deps[:todos] || []]

        {:ok, %{success: true, todo: todo},
         ContextUpdate.new() |> ContextUpdate.set(:todos, todos)}
      end

      def increment_counter(ctx, _args) do
        count = (ctx.deps[:counter] || 0) + 1

        {:ok, %{count: count},
         ContextUpdate.new() |> ContextUpdate.set(:counter, count)}
      end

      def add_note(ctx, %{"note" => note}) do
        {:ok, %{added: note},
         ContextUpdate.new() |> ContextUpdate.append(:notes, note)}
      end
    end

## Operations

- `set/3` - Replace a key's value
- `merge/3` - Deep merge a map into an existing map key
- `append/3` - Append an item to a list key
- `delete/2` - Remove a key
- `log_event/3` - Record a bookkeeping session event (touches no deps)

## Integration

The AgentRunner applies these updates to the context deps after tool execution:

    case execute_tool(tool, args, ctx) do
      {:ok, result, %ContextUpdate{} = update} ->
        new_ctx = ContextUpdate.apply(update, ctx)
        {:ok, result, new_ctx}

      {:ok, result} ->
        {:ok, result, ctx}
    end

# `operation`

```elixir
@type operation() ::
  {:set, atom(), any()}
  | {:merge, atom(), map()}
  | {:append, atom(), any()}
  | {:delete, atom()}
  | {:log_event, atom(), map()}
```

# `t`

```elixir
@type t() :: %Nous.Tool.ContextUpdate{operations: [operation()]}
```

# `append`

```elixir
@spec append(t(), atom(), any()) :: t()
```

Append an item to a list key in context deps.

If the key doesn't exist or is nil, creates a new list with the item.

## Example

    ContextUpdate.new()
    |> ContextUpdate.append(:history, %{action: "search", query: "elixir"})

# `apply`

```elixir
@spec apply(t(), Nous.Agent.Context.t()) :: Nous.Agent.Context.t()
```

Apply all operations to a context, returning the updated context.

Deps operations are applied first, in the order they were added, then
`log_event/3` events are appended to the session log, also in order.
`ctx.messages` is untouched: a bookkeeping event projects to no message,
which is the whole point of recording a tool's side effect this way.

## Example

    update = ContextUpdate.new()
    |> ContextUpdate.set(:key, "value")
    |> ContextUpdate.append(:list, "item")
    |> ContextUpdate.log_event(:tool_call, %{name: "search"})

    new_ctx = ContextUpdate.apply(update, ctx)

# `apply_to_run_context`

```elixir
@spec apply_to_run_context(t(), Nous.RunContext.t()) :: Nous.RunContext.t()
```

Apply all operations to a RunContext, returning the updated context.

For backwards compatibility with tools using RunContext.

**`log_event/3` operations are dropped here.** A `Nous.RunContext` carries
deps and the run seam, not a session log, so there is nowhere for an event to
go. The drop is logged at warning level naming the types lost, because a
silently discarded audit record is worse than none: it looks like it worked.
A tool whose events must survive has to run through `Nous.AgentRunner`, which
applies the same update to a `Nous.Agent.Context`.

# `delete`

```elixir
@spec delete(t(), atom()) :: t()
```

Delete a key from context deps.

## Example

    ContextUpdate.new()
    |> ContextUpdate.delete(:temp_data)

# `empty?`

```elixir
@spec empty?(t()) :: boolean()
```

Check if this ContextUpdate has any operations.

# `log_event`

```elixir
@spec log_event(t(), atom(), map()) :: t()
```

Record a bookkeeping session event alongside this update.

For a tool that did something auditable which must NOT enter the model's
history — a sub-dispatch made from inside a code run, say. The event is
appended to the session log through `Nous.Agent.Context.log_event/3`, which
projects to no message, so `ctx.messages` is unchanged.

`type` and `data` are the same pair `Nous.Agent.Context.log_event/3` takes: a
bookkeeping `Nous.Session.Event` type and its payload. A surface type
(`:user_message` and friends) is refused there, with a warning, so this
cannot be used to smuggle content into the transcript.

Only a `Nous.Agent.Context` has a log. Applied to a `Nous.RunContext` these
operations are dropped — loudly; see `apply_to_run_context/2`.

## Example

    ContextUpdate.new()
    |> ContextUpdate.log_event(:tool_call, %{id: "sub_1", name: "file_read"})

# `log_events`

```elixir
@spec log_events(t()) :: [{atom(), map()}]
```

The `{type, data}` pairs added by `log_event/3`, in the order they were added.

Deps operations are skipped. A caller that owns a session log uses this to
append them; a caller that does not uses it to say exactly what it dropped.

# `merge`

```elixir
@spec merge(t(), atom(), map()) :: t()
```

Deep merge a map into an existing map key in context deps.

If the key doesn't exist, it will be created with the map value.

## Example

    ContextUpdate.new()
    |> ContextUpdate.merge(:settings, %{theme: "dark"})

# `new`

```elixir
@spec new() :: t()
```

Create a new empty ContextUpdate.

## Example

    update = ContextUpdate.new()
    |> ContextUpdate.set(:key, "value")

# `operations`

```elixir
@spec operations(t()) :: [operation()]
```

Get the list of operations in this update.

# `set`

```elixir
@spec set(t(), atom(), any()) :: t()
```

Set a key to a value in the context deps.

Replaces any existing value for the key.

## Example

    ContextUpdate.new()
    |> ContextUpdate.set(:user_id, 123)

# `to_deps`

```elixir
@spec to_deps(t(), map()) :: map()
```

Fold this update's deps operations into a map, starting from `initial`.

This is the **single** reducer for `ContextUpdate` operations: `apply/2`,
`apply_to_run_context/2` and `Nous.AgentRunner.ToolExecution` all fold
through here, so an operation's meaning is defined in exactly one place.
There used to be three hand-synchronised copies, and they had already drifted
— the runner's did a shallow merge and could not see a new operation type at
all.

`:log_event` operations touch no deps and are skipped: only the caller knows
whether it holds something with a log to put them in.

## Example

    update = ContextUpdate.new() |> ContextUpdate.append(:log, :b)
    ContextUpdate.to_deps(update, %{log: [:a]})
    #=> %{log: [:a, :b]}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
