Nous.Session.Event (nous v0.17.1)

Copy Markdown View Source

One durable fact about a session.

Events are append-only. Nothing is ever mutated or deleted: a compaction appends a replace that shadows a range, and the shadowed events stay in the log where fork, rewind and audit can still see them.

Identity lives here, not on Nous.Message

%Nous.Message{} declares @primary_key false — messages genuinely have no identity, and adding one would change a public struct that Nous.run/3 hands back. The event carries seq; messages are projected fresh from the log every time. That is what lets the log be internal while result.messages stays byte-identical.

Surface and non-surface types

Only four types project into the model-visible surface, and only those may carry a :surface_op:

  • :system_message, :user_message, :assistant_message, :tool_result

Everything else is bookkeeping the model never sees — :tool_call, :turn_start, :turn_end, :step_start, :step_end, :request_header. They are logged because they are what makes a run reconstructable, but Nous.Session.Log.derive_messages/1 projects them to nothing.

Why four and not three

The plan specified exactly three surface types, on the assumption that system content is assembly-time state rather than history. That assumption does not survive contact with the code: Nous.Plugins.Summarization appends its summary as a system message mid-conversation, and build_initial_messages in Nous.AgentRunner puts one at the head. A system message is therefore a real, ordered part of the transcript and needs a surface type. The rewrite of the system prompt in Nous.AgentRunner.PromptAssembly stays assembly-time and is deliberately not an event — see that module.

Summary

Types

How this event contributes to the model-visible surface.

t()

Functions

Build an event, validating type, data shape and :surface_op.

Whether this event contributes to the model-visible surface.

The :surface_op this event carries. :append unless it says otherwise.

The types that project into the model-visible surface.

Every valid event type.

Types

surface_op()

@type surface_op() :: :append | {:replace, non_neg_integer(), non_neg_integer()}

How this event contributes to the model-visible surface.

:append adds it at the end. {:replace, start_seq, stop_seq} additionally shadows every surface event in that inclusive seq range — the mechanism behind non-destructive compaction.

t()

@type t() :: %Nous.Session.Event{
  data: map(),
  seq: non_neg_integer(),
  time: DateTime.t(),
  type: type()
}

type()

@type type() ::
  :system_message
  | :user_message
  | :assistant_message
  | :tool_result
  | :tool_call
  | :turn_start
  | :turn_end
  | :step_start
  | :step_end
  | :request_header

Functions

new(seq, type, data, time \\ nil)

@spec new(non_neg_integer(), type(), map(), DateTime.t() | nil) ::
  {:ok, t()} | {:error, term()}

Build an event, validating type, data shape and :surface_op.

Validation happens here, at append time, not at persist time. An event that cannot be serialized is a bug in the caller, and discovering it hours later when a session is saved makes it someone else's bug.

Returns {:error, reason} rather than raising: an event that fails validation must not take down a live agent run.

surface?(type)

@spec surface?(type() | t()) :: boolean()

Whether this event contributes to the model-visible surface.

Examples

iex> Nous.Session.Event.surface?(:assistant_message)
true

iex> Nous.Session.Event.surface?(:step_start)
false

surface_op(event)

@spec surface_op(t()) :: surface_op()

The :surface_op this event carries. :append unless it says otherwise.

surface_types()

@spec surface_types() :: [type()]

The types that project into the model-visible surface.

Examples

iex> Nous.Session.Event.surface_types()
[:system_message, :user_message, :assistant_message, :tool_result]

types()

@spec types() :: [type()]

Every valid event type.