Skip to content

Activity Tab: Events & Display Reference ​

How the Activity tab presents what an agent did. The activity log (activity.jsonl) is an append-only curated audit: it keeps user-visible and diagnostic events, while raw provider protocol frames are dropped before storage. This doc explains the display layer on top of that log: which persisted events surface, in which view, and what each row shows.

It describes current behavior, not a wishlist. Where a field genuinely isn't available yet, it's called out under Known gaps.

Sources ​

Seventeen modules import the activity service. Thirteen write to the log, four only read it. These two tables are the claim, and server/tests/activity-emitters.test.ts parses them out of this file and compares them against a set re-derived from the sources. There is no third list: edit a table, or the test reds.

Emitters ​

modulewrites
server/agents/agent.service.tsagent lifecycle
server/asks/interactive-ask.service.tsanima ask prompts and answers
server/inbox/attention-suggestion-activity.tsattention suggestions
server/inbox/reminder-subscriber.tspreflight error/recovery attention
server/inbox/slack-subscriber.tssubscription changes from Slack
server/inbox/subscription.service.tssubscription changes from the CLI
server/memory/memory-coherence-outcome.tsDreaming outcomes
server/reminders/reminder.activity.tsreminder schedule, fire, cancel
server/runtime/activity.tsturns, messages, provider events
server/runtime/send-hold.tssend-hold HELD local completed
server/messages/contact-policy.service.tsdo-not-contact send refusal
server/linear/contact-policy.tsLinear do-not-contact refusal
server/slack-interactions/shortcut.service.tsSlack shortcut invocations
server/tools/linear-api.tsLinear API request metadata and bounded result summaries
server/tools/tool-context.tstool steps

Readers ​

modulereads
server/diagnostics/agent-diagnostics.service.tsreadLastN for health output
server/memory/memory-coherence-scheduler.tsreadNewestUntil to find work
server/runtime/item-activities.tsreadAll to rebuild an item
server/web/agent-routes.tslistActivityFeed for the tab

server/inbox/subscription.service.ts writes and does not read; its .list() call belongs to SubscriptionStore. Display: web/src/lib/activities.ts, web/src/lib/activity-feed.ts, web/src/views/agents/activity/*.

Re-derive the emitter table yourself:

sh
# modules importing the activity service, by SPECIFIER, that also write
grep -rlE "from ['\"][^'\"]*activities/activity\.service\.js['\"]" --include='*.ts' server \
  | grep -v '/tests/' | grep -v 'activities/activity.service.ts' \
  | xargs grep -lE 'activityServiceForAgent\([^)]*\)[[:space:]]*\.[[:space:]]*record\(|: (ActivityRecorder|ActivityService)' \
  | sort

Three traps this encodes, each of which produced a wrong answer before it was written down.

Match the module specifier, never the imported name. server/reminders/ records through a ReminderActivityRecorder and never mentions activityServiceForAgent, so a symbol grep silently misses it.

Bind a call to its receiver. A bare .record( or .list( matches any object in the file: SubscriptionStore.list() reads as an activity read, and saveAsk(record: InteractiveAskRecord) reads as a write.

The service arrives three ways - called directly, injected as an ActivityService, or injected as an ActivityRecorder. A check that knows only the first two reports server/asks/interactive-ask.service.ts as neither reader nor writer.

A new emitter turns the test red. Add it to the table in the same commit.

What the Activity tab is ​

The Activity tab is our audited boundary made legible: it turns the conversational subset of persisted chat side effects, tool calls, provider events, and runtime transitions into something a human can actually read. The activity log is the source of truth for the curated audit; the tab is a readable projection of it.

Display principles ​

1. Three tiers decide whether an event shows, and where.

TierWhere it showsWhat lives here
ConversationThe timeline (messages + per-message step folds)What a human reads to follow what the agent did: its output, the files/commands/searches it touched, the messages it sent, reminders, subscriptions, failures.
AuditPersisted only (no UI surface today)Diagnostics worth having when inspecting but noise in the narrative: session/usage stats, rate limits, retries, model reroutes, provider warnings, low-level lifecycle. Kept in activity.jsonl; the feed builder can expose them (showHidden), but since the single-timeline rework no UI control does.
HiddenNever renderedRaw streaming/protocol frames, per-token deltas, *.system.init, *.context.stats, sanitized reasoning, legacy duplicate event names. Most of these are dropped before storage by design; if an old row exists, the UI never draws it.

Linear API requests use compact action rows in the timeline, including reads and failures. Their issue heading and result come from recorded metadata, with Details for the operation and outcome. Only an explicit successful mutation result confirms a change; a completed request alone remains unconfirmed. Session message labels preserve the recorded progress, question, response or error kind, and older messages keep generic labels.

The former "Show all steps" and "Failed only" controls were retired with the single-timeline rework: Conversation steps now fold behind a per-message ▸ N steps disclosure, and failure rows render in place with failure styling instead of behind a filter.

2. Each row is headline + the params that make it specific + the rest on expand.

Every shown row is a one-line headline anyone can scan, the 1–2 params that make it meaningful inline (the query, the path, the command, not just the verb), and, when the inline text is truncated or overflows, a chevron to expand the full untruncated text. The expand reveals the full content of that field (the whole command, the whole output), not a raw payload dump. Rows whose params fit don't get a chevron; message/file rows show their full body inline already.

3. Categories are by function; tier is orthogonal.

The catalog below is grouped into five functional categories so it's navigable. A category answers "what kind of thing is this event about"; the tier answers "should it show by default". The two are independent: a category mixes Conversation, Audit, and Hidden rows.

Event catalog ​

  1. Run lifecycle & agent output
  2. Provider tool calls
  3. Chat actions
  4. Reminders, subscriptions & session
  5. Provider internals & diagnostics

1. Run lifecycle & agent output ​

The agent's text output and the start/finish/fail/abort of a run.

EventWhen it firesTierShown inlineOn expand
agent.textProvider emits final assistant text.ConversationOutput + message text (truncated at 280 chars).Full text.
runtime.completedRuntime finishes an item successfully.Conversation (subtle)Idle (turn-end marker).—
runtime.failedRuntime throws or exits with an error.ConversationRun failed + first line of the error.Full error.
runtime.abortedItem aborted by restart drain, operator restart, shutdown, user stop, idle timeout, or a stalled run with a message waiting (stalled). requeuedFollowups counts follow-ups requeued because the provider never reached them.ConversationReason-mapped title: Runtime restarted / Stopped by user / Restarted by operator / Idle timeout / Stopped to answer messages / Runtime stopped.—
runtime.followup_failedAppending a follow-up to an active run failed.ConversationFollow-up failed + reason.Full reason/error.
runtime.startedRuntime starts processing an item.AuditWorking / Message received.—
runtime.pendingItem arrived while another was active and couldn't be appended.AuditQueued behind current item.—
runtime.followup_appendedFollow-up appended into an active run.AuditFollow-up added to current run + text snippet.Full text.
runtime.outputRaw stdout/stderr from a provider child process.AuditProcess output + [stream] + first line.Full text.
runtime.steered, runtime.steer_failed, runtime.provider_silentLegacy names predating the follow-up rename / watchdog diagnostic.Hidden——

2. Provider tool calls ​

Files, shell, search, web, and planning/subagent helpers the provider invokes. Provider tools currently emit a started row (used as the visible row) plus failures; successful completion rows aren't emitted yet (see Known gaps).

EventWhen it firesTierShown inlineOn expand
File/code tools: Read, ReadFile, ReadMediaFile, Write, WriteFile, Edit, Grep, Glob, WebFetch, FetchURL, fileChange, StrReplaceFile (started)Provider starts a file/read/edit/list/fetch tool.ConversationVerb (Read / Wrote / Edited / Listed / Fetched) + target (path / pattern). Edit rows can carry a diff on expand.Full target/diff if truncated.
Shell: Bash / shell (started)Provider runs a shell command.ConversationRan + command (shell wrapper stripped, truncated at 120 chars). Anima CLI wrapper commands dedupe against the first-class anima.* row.Full command.
Web search / research: WebSearch, SearchWeb, Codex webSearch (started)Provider runs a web search.ConversationSearched + the query inline (reads query/target).Full query.
Planning / delegation: Agent, Skill, TaskCreate, TaskUpdate, TodoWrite, SetTodoList (started)Provider starts a helper tool.ConversationMapped verbs: Delegated to subagent / Ran skill / Created task / task-status verb / Updated todos. Claude task rows include the available subject/active form, task ID, and status.Full target if truncated.
runtime.event:codex.plan.updatedCodex publishes a structured plan update.ConversationUpdated plan + completed count + current step.Full step/status list.
ToolSearch (started)Provider searches its deferred tools.AuditSearched tools.—
tool.call.failed (provider tools)A provider tool errors or a command exits non-zero.Conversation<verb> failed + target/command + terse error. Failed shell leads with the command.Full error.
(missing) legacy Kimi tool-calls without payload.toolPre-rename Kimi logs.Hidden——

For Claude TaskCreate and TaskUpdate, the task summary copies only subject, activeForm (or active_form), taskId (or id / task_id), and status from tool input. These become taskSubject, taskActiveForm, taskId, and taskStatus; target is derived from the same fields. It excludes description and arbitrary metadata, including nested values. This restriction applies to the task summary, not to the provider's own transcript.

For root Claude tasks, a successful structured create result associates its assigned ID with the input subject. Successful renames update that association; deletion removes it after the activity is recorded. The per-agent display cache survives turns and runtime restarts, isolates native session IDs, and retains up to 1,000 names. Updates with only an ID and status can then show the name. Unknown or evicted IDs, unscoped/subagent events, and unreadable cache files fall back to the provider summary. Existing activity history is not rewritten. Description and metadata never enter this cache.

3. Chat actions ​

Messages, files, reactions, and reads the agent performs in Slack or Feishu, plus the side-effect wrappers around the actual platform write. started rows are dropped: the completed/effect row is the visible action, and the audited external.effect.* row dedupes against the anima.* one.

EventWhen it firesTierShown inlineOn expand
external.effect.completed:slack.ask.post / tool=anima.askAgent posts a bounded Slack question with buttons.ConversationAsked <person> / Asked anyone + question + [option / option]; the plain raw anima ask ... shell row is deduped.Full question/options target if truncated.
choice_response inbox itemA human clicks a Slack ask option.Conversation (inbound)Inbound row from the answerer: Selected: <option> + original question; surface chip points to the original channel/thread/DM.Full body shown inline.
anima.ask.answerA human clicks a Slack ask option. Anima marks the ask answered and queues the choice_response item, then records this event. Rejected clicks (already answered, forbidden, not found, invalid option) are recorded with that outcome.Audit——
anima.shortcut.handoffA human hands a Slack message to the agent with the message shortcut. The message itself arrives as an inbox item.Audit——
Slack or Feishu inbox itemA message, mention, DM, thread, or topic reply wakes the agent.Conversation (inbound)Actor + rendered message body + platform-appropriate surface chip. Slack mention and formatting forms are normalized before display.Full body shown inline.
anima.message.send / update (completed), external.effect.completed:slack.message.* / feishu.message.*Agent posts or edits a chat message.Conversation (message row)Replied / Replied in thread / Edited + surface chip + full message body. Subtle warning badge when warnings present.Full body shown inline.
anima.file.send (completed), external.effect.completed:slack.file.send / feishu.file.sendAgent sends one or more files.Conversation (message row)File row + caption + attachment cards.Full body shown inline.
anima.message.react (completed), external.effect.completed:slack.reaction / feishu.reactionAgent adds/removes a reaction.Conversation (light)Reaction glyph/name + surface chip; subtle noop ("already present/absent").—
anima.message.read (completed)Agent reads Slack or Feishu history.ConversationRead messages + conversation · slice (thread / around <time> / last N) · message count.Full target if truncated.
anima.message.* (failed), external.effect.failed:slack.* / feishu.*A chat message/file/react/read action fails.ConversationProduct title (Message failed / Edit failed / File upload failed / Reaction failed / Read failed) + error, matched by tool or effect.Full error.
tool.call.started:anima.*, external.effect.started:slack.* / feishu.*The pre-write/started phase of an Anima chat action.Hidden (dropped)——

4. Reminders, subscriptions & session ​

Agent-owned schedules, channel/thread subscriptions, and provider-session rotation.

EventWhen it firesTierShown inlineOn expand
anima.reminder.schedule (completed)Agent schedules a reminder.ConversationScheduled reminder + title · at <nextDueAt>.—
anima.reminder.cancel (completed)Reminder cancelled.ConversationCancelled reminder + title.—
anima.reminder.snooze (completed)Reminder snoozed.ConversationSnoozed reminder + until <time>.—
anima.reminder.fire (completed)Reminder wake fires and is enqueued.ConversationConsumed into the inbound row: Reminder / Reminder · fire #N.—
anima.reminder.preflight (completed)Hosted preflight result applied (succeeded/declined/errored). Best-effort audit after commit; no command or streams.ConversationPreflight succeeded / declined / errored + title · duration/exit.—
anima.reminder.list (completed)Legacy: no longer recorded. Old rows remain.AuditListed reminders.—
anima.reminder.* (failed)A reminder action fails.ConversationReminder <action> failed + error.Full error.
anima.subscription.addA Slack mention creates a channel/thread subscription.ConversationSubscribed to + #channel · kind (channel/thread).—
anima.subscription.removeLegacy: replaced by mute/unmute. Old rows still render.ConversationUnsubscribed from + #channel.—
anima.subscription.mute, anima.subscription.unmuteAgent mutes or unmutes a followed channel or thread (anima subscription mute / unmute).AuditNone yet: no renderer—
anima.session.rotateUser rotates a provider session, or the runtime archives one during automatic corruption recovery.ConversationSession rotated + Archived N provider sessions · note.—
memory_coherence.outcomeA scheduled Dreaming wake finishes.Conversation (failure styling when it failed)Dreaming updated memory / Dreaming changed nothing / Dreaming failed.—
anima.attention.suggestionAnima attaches an attention suggestion to an incoming item, such as a reminder preflight error or recovery.ConversationAttention system event with the suggestion and its channel/thread.—
tool.call.started:anima.reminder.scheduleThe started phase of a schedule.Hidden (dropped)——

5. Provider internals & diagnostics ​

Compaction, usage/rate-limit stats, retries, and raw streaming frames. Mostly Audit or Hidden: these explain how the provider ran, not what the agent did.

EventWhen it firesTierShown inlineOn expand
*.compact.completedProvider finishes context compaction.Conversation (subtle)Context compacted (a memory/continuity beat).—
*.compact.failedContext compaction fails.ConversationCompact failed + error.Full error.
*.compact.startedProvider begins compaction.Conversation (subtle)Compacting context.—
*.session.statsProvider reports turn/session usage.AuditSession stats + model · context · cached · output.—
claude.rate_limitClaude reports rate-limit status.AuditRate limit updated + status / type / reset when available.—
codex.rate_limits.updatedCodex reports updated rate-limit windows.AuditRate limit updated + limit label + reset.—
codex.model.reroutedCodex switches model mid-run.AuditModel rerouted + from → to + reason.—
codex.warning (and warning variants)Codex emits a warning/config/deprecation notice.AuditProvider warning + message/summary.Full details.
codex.protocol.invalid_jsonCodex writes a non-JSON line where JSON-RPC was expected.AuditSurfaces generically when flagged warning/failed.Full error.
claude.provider.retrySide-effect-free transient error; Anima retries the turn.AuditProvider retry + reason.Full error.
claude.provider.resume_retryTransient error after partial progress; Anima resumes instead of repeating side effects.AuditProvider resume retry + reason.Full error.
provider.crash.retryProvider child crashes; Anima retries the same inbox item with a recovery notice.AuditProvider retry + attempt/max + error.Full error.
provider.transient.retryTransient provider API error (5xx/overload, network or stream stall, safeguard false positive); Anima re-sends the same item on the same session after backoff (5s / 30s / 2min, 3 attempts).AuditProvider retry + attempt/max + error.Full error.
provider.rate_limit.deferProvider rate limit or session quota; Anima parks the item until the reported reset (30s–6h, default 5min) instead of failing it. Bounded to 6 deferrals per item.AuditRate limit + resume time.Full error.
runtime.failure_noticeItem failed for good (retries exhausted or terminal error); Anima replied in the originating DM/mention so the requester knows to resend.AuditFailure notice + reason.—
claude.session.resume_missingStored provider session can't resume; a fresh one starts.AuditProvider session expired / Started a fresh session.—
grok.session.load_missing, kimi.session.resume_missing, pi.session.resume_missing, antigravity.session.resume_missingStored ACP or pi session can't resume; the adapter starts a fresh one.Hidden——
grok.model.catalogGrok records the model authority it reported.Hidden——
*.turn.started, *.turn.completed (Grok, Kimi, pi, Antigravity)Per-prompt lifecycle telemetry. Dropped before storage (shared/runtime-event-noise.ts); only old rows exist.Hidden——
runtime.restart_resumedA restart-drained inbox item resumes after service restart.Hidden——
kimi.approval.responseLegacy: no longer recorded. Old rows still render.AuditApproval answered + response.—
*.context.statsContinuous mid-run context-window telemetry.Hidden——
provider.reasoningSanitized reasoning snippet (not persisted).Hidden——
*.system.initProvider session initialization.Hidden——
codex.model.verification, claude.system.statusLow-signal provider status.Hidden——
Streaming frames: *.stream.*, *outputDelta, *.thinking.delta, *.content.part, *.tool.call.part, *.tool_result, hooks, plan display / non-Codex plan updates, diff, turn/stepRaw provider protocol and per-token telemetry.Hidden——
runtime.event:assistant, runtime.event:system.api_retryLegacy diagnostics, superseded.Hidden——

Known gaps ​

A few rows can't yet show everything because the field isn't emitted. These are emission-side (backend), not rendering:

  1. Provider tool success carries no result. Provider adapters emit tool.call.started + failures, but no tool.call.completed for success, so there's no result summary to attach to a tool row (search → N results, read → bytes/lines, fetch → status). Surfacing those needs a completed row (or a result attached to the started row).
  2. Some historical/unknown provider tools still lack a target. Known file/shell/search tools now derive target/command where the provider gives enough input, but old rows and provider-specific tools outside the catalog can still fall back to a bare verb until their input shape is mapped.

The Codex webSearch query (previously missing from the log) is now emitted, so the search query shows inline (the original "Research shows only Research" defect). Logged here as resolved for context.