Fen contracts

events

Event-bus shapes.

agent-shutdown

Emitted once per run during teardown. :error is present for crashed paths.

Fields for agent-shutdown event contract
FieldTypeRequiredSummary
:agent AgentyesAgent instance associated with the event.
:error stringCrash details when shutdown follows an error path.
:reason keywordyes:normal | :crashed
:type :agent-shutdownyesEvent discriminator for :agent-shutdown events.

agent-started

Emitted once per run after setup and before the first new step.

Fields for agent-started event contract
FieldTypeRequiredSummary
:agent AgentyesAgent instance associated with the event.
:cwd stringyesWorking directory for the active agent run.
:model stringyesModel identifier selected for the active run.
:provider keyword|stringyesProvider name selected for the active run.
:type :agent-startedyesEvent discriminator for :agent-started events.

agent-turn-complete

Emitted once per submitted user turn after the agent coroutine finishes and the presenter busy flag has been cleared.

Fields for agent-turn-complete event contract
FieldTypeRequiredSummary
:agent AgentyesAgent instance associated with the completed turn.
:error stringError summary when the turn ended with :status :error.
:message-count numberyesConversation message count after the turn finished.
:result stringFinal visible assistant text for successful or cancelled turns, when available.
:status keywordyes:ok | :cancelled | :error
:type :agent-turn-completeyesEvent discriminator for :agent-turn-complete events.

assistant-stream-end

Stream finished. Emitted once after all per-block end events for a single AssistantMessage.

Fields for assistant-stream-end event contract
FieldTypeRequiredSummary
:type :assistant-stream-endyesEvent discriminator for :assistant-stream-end events.

assistant-text

Final visible text emitted by the assistant. One per AssistantMessage with text blocks.

Fields for assistant-text event contract
FieldTypeRequiredSummary
:final? booleanTrue when this is the final aggregate payload for the current block.
:text stringyesComplete assistant text payload for this event.
:type :assistant-textyesEvent discriminator for :assistant-text events.

assistant-text-delta

Streaming text token. Aggregated by presenters during a stream.

Fields for assistant-text-delta event contract
FieldTypeRequiredSummary
:delta stringyesIncremental streamed text fragment for the open content block.
:type :assistant-text-deltayesEvent discriminator for :assistant-text-delta events.

assistant-thinking

Final reasoning text from the assistant (for providers that surface reasoning content).

Fields for assistant-thinking event contract
FieldTypeRequiredSummary
:final? booleanTrue when this is the final aggregate payload for the current block.
:text stringyesComplete assistant text payload for this event.
:type :assistant-thinkingyesEvent discriminator for :assistant-thinking events.

assistant-thinking-delta

Streaming reasoning token.

Fields for assistant-thinking-delta event contract
FieldTypeRequiredSummary
:delta stringyesIncremental streamed text fragment for the open content block.
:type :assistant-thinking-deltayesEvent discriminator for :assistant-thinking-delta events.

cancelled

Cooperative cancel observed; the current step appended an aborted assistant message.

Fields for cancelled event contract
FieldTypeRequiredSummary
:type :cancelledyesEvent discriminator for :cancelled events.

compaction-summary

Context compaction completed and produced a summary for older messages.

Fields for compaction-summary event contract
FieldTypeRequiredSummary
:guidance stringOptional user guidance supplied to /compact.
:messages-kept numberNumber of recent messages kept verbatim.
:messages-summarized numberNumber of older messages summarized.
:summary stringyesSummary text installed into the compacted model context.
:tokens-after numberApproximate context tokens after compaction.
:tokens-before numberApproximate context tokens before compaction.
:trigger keywordWhy compaction ran, such as :manual.
:type :compaction-summaryyesEvent discriminator for :compaction-summary events.

dismiss

Presenter signal that an open overlay/picker should close.

Fields for dismiss event contract
FieldTypeRequiredSummary
:type :dismissyesEvent discriminator for :dismiss events.

done

Provider stream: terminal event for a successful AssistantMessage.

Fields for done event contract
FieldTypeRequiredSummary
:message AssistantMessageyesCanonical assistant message produced by the provider stream.
:type :doneyesEvent discriminator for :done events.

error

Generic surface error — typically command-dispatch or user-input failures.

Fields for error event contract
FieldTypeRequiredSummary
:error stringyesHuman-readable error message for presenters and logs.
:type :erroryesEvent discriminator for :error events.

extension-error

Emitted when an extension event handler raises. Suppressed for recursive extension-error events to prevent loops.

Fields for extension-error event contract
FieldTypeRequiredSummary
:error stringyesHuman-readable error message for presenters and logs.
:event keyword|stringOriginal event being handled when the extension error occurred.
:owner keyword|stringExtension owner whose handler raised, when known.
:type :extension-erroryesEvent discriminator for :extension-error events.

extension-loaded

Emitted by the loader for each successfully loaded extension manifest.

Fields for extension-loaded event contract
FieldTypeRequiredSummary
:first-party? booleanTrue when the loaded extension came from fen's bundled extension set.
:name stringyesExtension manifest name that was loaded.
:type :extension-loadedyesEvent discriminator for :extension-loaded events.

info

Transient informational message intended for the presenter status row or panel.

Fields for info event contract
FieldTypeRequiredSummary
:info stringHuman-readable informational message payload.
:text stringAlternative human-readable informational text.
:type :infoyesEvent discriminator for :info events.

llm-end

Provider call completed (success or error). The :message field carries the canonical AssistantMessage.

Fields for llm-end event contract
FieldTypeRequiredSummary
:message AssistantMessageyesCanonical assistant message produced by the provider stream.
:type :llm-endyesEvent discriminator for :llm-end events.

llm-start

Provider call beginning.

Fields for llm-start event contract
FieldTypeRequiredSummary
:model stringModel identifier used for the provider request, when known.
:provider keywordProvider name for the outbound LLM call, when known.
:type :llm-startyesEvent discriminator for :llm-start events.

message-appended

Emitted by fen.core.agent immediately after agent.messages grows.

Fields for message-appended event contract
FieldTypeRequiredSummary
:agent AgentyesAgent instance associated with the event.
:index numberyes1-based index of the appended message.
:message MessageyesCanonical message that was appended to the conversation.
:type :message-appendedyesEvent discriminator for :message-appended events.

queued

User-line queued while the agent is busy. Consumed by the agent on natural-stop.

Fields for queued event contract
FieldTypeRequiredSummary
:line stringyesRaw user input line associated with the event.
:type :queuedyesEvent discriminator for :queued events.

redraw

Presenter hint that a registered panel needs to be repainted.

Fields for redraw event contract
FieldTypeRequiredSummary
:type :redrawyesEvent discriminator for :redraw events.

reinit-presenter

Presenter signal that the UI should be torn down and re-initialized (used by /reload).

Fields for reinit-presenter event contract
FieldTypeRequiredSummary
:type :reinit-presenteryesEvent discriminator for :reinit-presenter events.

reset-conversation

Presenter signal that the active conversation should be cleared (used by /new).

Fields for reset-conversation event contract
FieldTypeRequiredSummary
:type :reset-conversationyesEvent discriminator for :reset-conversation events.

set-status-info

Presenter-level status hint. Owners post a transient status line; nil clears.

Fields for set-status-info event contract
FieldTypeRequiredSummary
:info string|nilTransient status text to show, or nil to clear it.
:type :set-status-infoyesEvent discriminator for :set-status-info events.

start

Provider stream opened. Marker event emitted before any block events.

Fields for start event contract
FieldTypeRequiredSummary
:type :startyesEvent discriminator for :start events.

text-delta

Provider stream: incremental text token within the open block.

Fields for text-delta event contract
FieldTypeRequiredSummary
:content-index numberyesPosition within the assistant's content array.
:delta stringyesIncremental streamed text fragment for the open content block.
:type :text-deltayesEvent discriminator for :text-delta events.

text-end

Provider stream: TextContent block closed; full text supplied.

Fields for text-end event contract
FieldTypeRequiredSummary
:content stringyesComplete text or thinking content for the closed stream block.
:content-index numberyesPosition within the assistant's content array.
:type :text-endyesEvent discriminator for :text-end events.

text-start

Provider stream: a TextContent block is starting.

Fields for text-start event contract
FieldTypeRequiredSummary
:content-index numberyesPosition within the assistant's content array.
:type :text-startyesEvent discriminator for :text-start events.

thinking-delta

Provider stream: incremental reasoning token within the open block.

Fields for thinking-delta event contract
FieldTypeRequiredSummary
:content-index numberyesPosition within the assistant's content array.
:delta stringyesIncremental streamed text fragment for the open content block.
:type :thinking-deltayesEvent discriminator for :thinking-delta events.

thinking-end

Provider stream: ThinkingContent block closed; full text supplied.

Fields for thinking-end event contract
FieldTypeRequiredSummary
:content stringyesComplete text or thinking content for the closed stream block.
:content-index numberyesPosition within the assistant's content array.
:type :thinking-endyesEvent discriminator for :thinking-end events.

thinking-start

Provider stream: a ThinkingContent block is starting.

Fields for thinking-start event contract
FieldTypeRequiredSummary
:content-index numberyesPosition within the assistant's content array.
:type :thinking-startyesEvent discriminator for :thinking-start events.

tool-call

Tool call about to execute. Carries the canonical ToolCall block.

Fields for tool-call event contract
FieldTypeRequiredSummary
:tool-call ToolCallyesCanonical tool-call block associated with this event.
:type :tool-callyesEvent discriminator for :tool-call events.

tool-call-delta

Provider stream: incremental JSON-text fragment for the open ToolCall arguments. Some providers stream arguments token-by-token; consumers may concatenate.

Fields for tool-call-delta event contract
FieldTypeRequiredSummary
:content-index numberyesPosition within the assistant's content array.
:delta stringyesIncremental streamed text fragment for the open content block.
:type :tool-call-deltayesEvent discriminator for :tool-call-delta events.

tool-call-end

Provider stream: ToolCall block closed; complete canonical ToolCall block supplied.

Fields for tool-call-end event contract
FieldTypeRequiredSummary
:content-index numberyesPosition within the assistant's content array.
:tool-call ToolCallyesCanonical tool-call block associated with this event.
:type :tool-call-endyesEvent discriminator for :tool-call-end events.

tool-call-start

Provider stream: a ToolCall block is starting; arguments not yet known.

Fields for tool-call-start event contract
FieldTypeRequiredSummary
:content-index numberyesPosition within the assistant's content array.
:type :tool-call-startyesEvent discriminator for :tool-call-start events.

tool-result

Tool execution finished. Carries the canonical ToolResultMessage.

Fields for tool-result event contract
FieldTypeRequiredSummary
:result ToolResultMessageyesCanonical tool-result message produced by the tool executor.
:type :tool-resultyesEvent discriminator for :tool-result events.

user

User-submitted line accepted by the presenter input layer. Distinct from :queued (which fires when the line is queued during a busy turn).

Fields for user event contract
FieldTypeRequiredSummary
:line stringyesRaw user input line associated with the event.
:type :useryesEvent discriminator for :user events.

types

Canonical message/tool types.

AgentContext

Per-call payload handed to a provider's :complete.

Fields for AgentContext type contract
FieldTypeRequiredSummary
:max-tokens numberyesMaximum output token budget requested for the provider call.
:messages [Message]yesCanonical conversation history to convert into provider wire format.
:system-prompt string|nilFully rendered system prompt for the current step, or nil when omitted.
:tools [Tool]yesProvider-visible tool specs available for this step.

AgentTool

Tool extended with execution metadata for the agent loop. Registered through (api.register :tool ...).

Fields for AgentTool type contract
FieldTypeRequiredSummary
:description stringyesProvider-facing description included in the tool schema.
:execute (args ?yield-fn) -> AgentToolResultyesRuntime callback that executes the tool with decoded arguments.
:label stringUI label.
:name stringyesRegistry name used to merge and dispatch the tool.
:parameters JSONSchemayesProvider-facing argument schema used for validation and prompting.

AgentToolResult

Outcome of a tool execution.

Fields for AgentToolResult type contract
FieldTypeRequiredSummary
:content [TextContent]yesText content returned to the provider as the tool observation.
:details anyOpaque presenter payload (UI-only).
:is-error? booleanyesTrue when the tool observation represents a failed call.

AssistantMessage

Single model response. Content is always an array, even when empty.

Fields for AssistantMessage type contract
FieldTypeRequiredSummary
:api keyword:openai-completions | :openai-responses | :anthropic-messages | :openai-codex
:content [TextContent|ThinkingContent|ToolCall]yesOrdered assistant output blocks, including visible text, reasoning, and tool calls.
:error-message stringPresent only when stop-reason = :error.
:model stringProvider model identifier that produced this response.
:provider keywordRegistered provider :name (e.g. :openai, :anthropic).
:role :assistantyesMessage role discriminator for model-authored turns.
:stop-reason StopReasonNormalized reason the provider stopped generation.
:timestamp numberyesMilliseconds since epoch when the assistant message was recorded.
:usage UsageOptional token accounting returned by the provider for this response.

Message

Union of UserMessage, AssistantMessage, ToolResultMessage. Stored on agent.messages and passed to providers in AgentContext.messages.

Members for Message type contract
MemberKindSummary
UserMessage variantUserMessage is a Message union variant.
AssistantMessage variantAssistantMessage is a Message union variant.
ToolResultMessage variantToolResultMessage is a Message union variant.

StopReason

Why the assistant stopped producing output.

Members for StopReason type contract
MemberKindSummary
:stop enum:stop is a StopReason enum value.
:length enum:length is a StopReason enum value.
:tool-use enum:tool-use is a StopReason enum value.
:error enum:error is a StopReason enum value.
:aborted enum:aborted is a StopReason enum value.

TextContent

Plain visible text block.

Fields for TextContent type contract
FieldTypeRequiredSummary
:text stringyesVisible UTF-8 text payload.
:type :textyesContent-block discriminator for visible text.

ThinkingContent

Reasoning/extended-thinking block. Surfaces both Anthropic extended thinking and OpenAI reasoning items.

Fields for ThinkingContent type contract
FieldTypeRequiredSummary
:redacted? booleanTrue when the provider redacted visible text.
:thinking stringyesReasoning or extended-thinking text emitted by the provider.
:thinking-signature stringOpaque echo signature; required for multi-turn extended thinking.
:type :thinkingyesContent-block discriminator for provider reasoning text.

Tool

Provider-agnostic tool spec — what providers see in AgentContext.tools.

Fields for Tool type contract
FieldTypeRequiredSummary
:description stringyesProvider-visible explanation of when and how to call the tool.
:name stringyesProvider-visible tool name used in tool-call blocks.
:parameters JSONSchemayes{:type :object :properties {...} :required [...]}

ToolCall

Assistant request to invoke a tool. Arguments are a parsed Lua table — providers JSON-decode wire arguments before constructing this block.

Fields for ToolCall type contract
FieldTypeRequiredSummary
:arguments tableyesDecoded argument table validated by the target tool implementation.
:id stringyesProvider- or agent-generated id used to match the eventual tool result.
:name stringyesRegistered tool name to execute.
:type :tool-callyesContent-block discriminator for tool invocation requests.

ToolResultMessage

Result of a single tool call, carried back to the provider on the next turn.

Fields for ToolResultMessage type contract
FieldTypeRequiredSummary
:content [TextContent]yesTool output blocks returned to the provider on the next turn.
:details anyOpaque presenter payload (UI-only).
:is-error? booleanyesTrue when the tool result should be treated as an error observation.
:role :tool-resultyesMessage role discriminator for tool execution results.
:timestamp numberyesMilliseconds since epoch when the tool result was recorded.
:tool-call-id stringyesMatches the originating ToolCall.id.
:tool-name stringyesTool name that produced the result, copied from the originating call.

Usage

Token usage counters returned by the provider (best-effort — providers fill what they can).

Fields for Usage type contract
FieldTypeRequiredSummary
:cache-read numberProvider cache-read tokens credited for the response.
:cache-write numberProvider cache-write tokens billed or recorded for the response.
:input numberInput or prompt tokens counted for the response.
:output numberGenerated output tokens counted for the response.
:total-tokens numberProvider-reported total tokens, or the best available aggregate.

UserMessage

Single user turn. Content is either a plain string or an array of TextContent blocks.

Fields for UserMessage type contract
FieldTypeRequiredSummary
:content string|[TextContent]yesVisible user input as plain text or canonical text content blocks.
:role :useryesMessage role discriminator for user-authored turns.
:timestamp numberyesMilliseconds since epoch.

register-kinds

Extension API register kinds.

auth-backend

Auth credential backend. Resolves an api-key or rotates an OAuth token for one or more providers.

Fields for auth-backend register-kind contract
FieldTypeRequiredSummary
:api-key () -> string|nilyesReturn the current bearer/API key, refreshing or loading secrets as needed.
:login! (opts) -> anyOptional. Drives fen --login <name>.
:logout! () -> anyOptional. Drives fen --logout <name>.
:name keyword|stringyesAuth backend name selected by provider configuration or CLI login/logout flags.

command

Slash command contribution. Looked up by name when the user submits /<name> <args> from a presenter.

Fields for command register-kind contract
FieldTypeRequiredSummary
:description stringHuman-readable command help shown in command listings and docs.
:handler (args caller-state) -> anyyesCallback invoked with raw command arguments and the caller's runtime state.
:idle-only? booleanRefuse the command while the agent is busy.
:name keyword|stringyesCommand name without the leading slash.
:order numberSort hint for /help.

control

Keyboard/UI control surface for presenters that support typed input bindings.

Fields for control register-kind contract
FieldTypeRequiredSummary
:description stringHuman-readable explanation of the control's UI effect.
:handler (ctx) -> anyyesCallback invoked by the presenter when the control is activated.
:name keyword|stringyesStable control name exposed to presenter help and docs.

hook

Lifecycle hook (currently before-tool). Inspects a tool call before it executes.

Fields for hook register-kind contract
FieldTypeRequiredSummary
:before-tool (tool-name args ctx) -> anyyesReturn {:block true :reason string} to veto.

introspect

Read-only extension state snapshot provider. Collected on demand for agent_state, /extensions, and runtime diagnostics.

Fields for introspect register-kind contract
FieldTypeRequiredSummary
:description stringHuman-readable description shown by diagnostics and docs.
:name keyword|stringyesOwner-scoped snapshot name. Multiple extensions may reuse the same name.
:snapshot (ctx) -> tableyesCheap, side-effect-free thunk returning JSON-friendly data. It is pcall-isolated and should not expose secrets.

panel

Non-modal side panel contribution rendered by presenters that support panels.

Fields for panel register-kind contract
FieldTypeRequiredSummary
:enabled? (ctx) -> booleanOptional predicate that hides the panel without unregistering it.
:name keyword|stringyesStable panel name used for toggles, docs, and diagnostics.
:render (ctx) -> anyyesCallback returning rows or presenter-specific content for the panel body.
:title stringOptional display title when the presenter renders panel chrome.

presenter

UI driver. Owns the input/output loop. Exactly one is active per run; the loader picks based on flags and manifest hints.

Fields for presenter register-kind contract
FieldTypeRequiredSummary
:init (ctx) -> nilOptional lifecycle callback run before the presenter loop starts.
:name keyword|stringyesPresenter name selected by CLI flags or extension activation rules.
:run (ctx) -> nilyesMain input/output loop for the selected presenter.
:shutdown (ctx) -> nilOptional lifecycle callback used to release terminal, socket, or other UI resources.

provider

LLM provider contribution. See the :provider-interface contract for the required record.

Fields for provider register-kind contract
FieldTypeRequiredSummary
:api keywordyesProtocol family (:openai-completions, :anthropic-messages, ...). Many providers may share an :api.
:build-body (model ctx options) -> tableyesBuild the provider request body from a model id, AgentContext, and call options.
:complete (model ctx options ?on-event ?yield-fn) -> AssistantMessageyesHigh-level provider entry point that runs one model turn and returns a canonical assistant message.
:convert-messages ([Message]) -> [WireMessage]yesTranslate canonical conversation messages into this provider's wire message format.
:convert-tools ([Tool]) -> [WireTool]yesTranslate canonical tool specs into this provider's wire tool schema.
:map-stop-reason (string) -> StopReasonyesNormalize provider stop reasons into fen's canonical StopReason enum.
:name keyword|stringyesProvider name selected by settings or CLI options.
:parse-response (WireResponse) -> AssistantMessageyesConvert a non-streaming provider response payload into an AssistantMessage.

session-backend

Persistence backend for canonical JSONL-style sessions. The --session flag selects one and fen.core.extensions.register.session_backend.set-active! activates it.

Fields for session-backend register-kind contract
FieldTypeRequiredSummary
:append (session message) -> nilyesPersist one canonical Message to the active session.
:close (session) -> nilyesFlush and release backend resources for an open session.
:find (opts) -> [SessionInfo]yesSearch sessions using backend-specific filters.
:latest (opts) -> SessionInfo|nilyesReturn the newest matching session, if any.
:list (opts) -> [SessionInfo]yesList sessions visible to this backend in a stable display order.
:load (path opts) -> [Message]yesRead canonical messages from a stored session path.
:name keyword|stringyesSession backend name selected by CLI flags or extension configuration.
:open (opts) -> sessionyesCreate or open the active session for a new run.
:open-existing (path opts) -> sessionyesOpen an existing session file or directory for appending.

status

Status-line contributor — produces a short string for the presenter's status row.

Fields for status register-kind contract
FieldTypeRequiredSummary
:name keyword|stringyesStable status item name used for sorting, diagnostics, and docs.
:order numberSort hint within the status side; lower values render earlier.
:render (ctx) -> string|nilyesCallback returning the current status text, or nil/empty text to hide it.

tool

Agent tool contribution. Merged into the per-step AgentContext.tools and dispatched by name when the assistant emits a ToolCall.

Fields for tool register-kind contract
FieldTypeRequiredSummary
:description stringyesProvider-facing guidance explaining when the model should call this tool.
:execute (args ?yield-fn) -> AgentToolResultyesRuntime callback that executes decoded arguments and returns canonical tool content.
:label stringOptional short UI label shown by presenters while the tool runs.
:name stringyesUnique tool name exposed to providers and matched against ToolCall.name.
:parameters JSONSchemayesJSON object schema describing the tool arguments providers may emit.

interfaces

Provider/auth/session interface records.

auth-backend

Required record shape for (api.register :auth-backend ...).

Members for auth-backend interface contract
MemberKindSummary
:api-key method:api-key is a required auth-backend interface method.
:login! optional-method:login! is an optional auth-backend interface method.
:logout! optional-method:logout! is an optional auth-backend interface method.

provider

Required record shape for (api.register :provider ...). See the :provider register-kind for field details.

Members for provider interface contract
MemberKindSummary
:complete method:complete is a required provider interface method.
:convert-messages method:convert-messages is a required provider interface method.
:convert-tools method:convert-tools is a required provider interface method.
:map-stop-reason method:map-stop-reason is a required provider interface method.
:parse-response method:parse-response is a required provider interface method.
:build-body method:build-body is a required provider interface method.

session-backend

Required record shape for (api.register :session-backend ...).

Members for session-backend interface contract
MemberKindSummary
:open method:open is a required session-backend interface method.
:open-existing method:open-existing is a required session-backend interface method.
:append method:append is a required session-backend interface method.
:close method:close is a required session-backend interface method.
:load method:load is a required session-backend interface method.
:find method:find is a required session-backend interface method.
:list method:list is a required session-backend interface method.
:latest method:latest is a required session-backend interface method.