{"id":51440622,"url":"https://github.com/ccarvalho-eng/animus","last_synced_at":"2026-07-05T11:01:45.077Z","repository":{"id":363651170,"uuid":"1238811301","full_name":"ccarvalho-eng/animus","owner":"ccarvalho-eng","description":"Operational memory for Elixir and OTP systems.","archived":false,"fork":false,"pushed_at":"2026-05-25T14:23:48.000Z","size":166,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-09T20:27:42.446Z","etag":null,"topics":["elixir","otp"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ccarvalho-eng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-14T13:29:11.000Z","updated_at":"2026-05-14T20:11:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ccarvalho-eng/animus","commit_stats":null,"previous_names":["ccarvalho-eng/animus"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ccarvalho-eng/animus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccarvalho-eng%2Fanimus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccarvalho-eng%2Fanimus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccarvalho-eng%2Fanimus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccarvalho-eng%2Fanimus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ccarvalho-eng","download_url":"https://codeload.github.com/ccarvalho-eng/animus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ccarvalho-eng%2Fanimus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35151638,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","otp"],"created_at":"2026-07-05T11:01:43.991Z","updated_at":"2026-07-05T11:01:45.047Z","avatar_url":"https://github.com/ccarvalho-eng.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Animus\n\nOperational memory for Elixir and OTP systems.\n  \n[![CI](https://github.com/ccarvalho-eng/animus/actions/workflows/ci.yml/badge.svg)](https://github.com/ccarvalho-eng/animus/actions/workflows/ci.yml)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)\n[![Elixir](https://img.shields.io/badge/elixir-1.17%2B-4B275F.svg)](mix.exs)\n[![OTP](https://img.shields.io/badge/otp-27%2B-6B4FBB.svg)](mix.exs)\n\nAnimus reconstructs what distributed systems experienced. It combines runtime\nmemories, telemetry, trace context, and process lifecycle signals into causal\ntimelines that help engineers understand what happened, why it happened, and\nwhich actor or dependency propagated failure.\n\nAnimus is not another logging platform, metrics dashboard, or generic APM clone.\nIt is OTP-native forensic tooling for production incident reconstruction.\n\nSee [Concepts](docs/product-space.md) for the core ideas behind memories,\ncausality, crash inspection, and the bleeding effect.\n\n## Why It Exists\n\nProduction incidents rarely live in one signal. A failed request may involve a\nPhoenix controller, a worker process, an external timeout, retry amplification,\nmailbox pressure, and a supervisor restart. Logs, metrics, and traces show\npieces. Animus preserves the operational memory that connects them.\n\nUse Animus when you need a forensic view of an OTP incident: the timeline, the\nactors involved, the pressure signals before failure, and the patterns that\nremain after the trace is over.\n\n## Quickstart\n\nAdd Animus to your application, configure the host repo, and install the\nmigration:\n\n```elixir\ndef deps do\n  [\n    {:animus, \"~\u003e 0.1\"}\n  ]\nend\n```\n\n```elixir\nconfig :animus,\n  repo: MyApp.Repo,\n  adapters: [:phoenix, :ecto, :oban],\n  retention_days: 30\n```\n\n```sh\nmix animus.install\nmix ecto.migrate\n```\n\nAfter an incident, query the trace:\n\n```elixir\nAnimus.timeline(trace_id)\nAnimus.inspect_crash(trace_id)\nAnimus.bleeding_effect(trace_id)\n```\n\n## Example: Payment Failure\n\nInstall automatic adapters for the signals your app already emits:\n\n```elixir\nconfig :animus,\n  repo: MyApp.Repo,\n  adapters: [:phoenix, :ecto, :oban]\n```\n\n```elixir\nAnimus.trace(\"payment_flow\", [trace_id: request_id], fn -\u003e\n  context = Animus.current_context()\n  {:ok, worker} = PaymentWorker.start(animus_context: context, payment: payment)\n  {:ok, _watch_ref} = Animus.watch(worker)\n\n  Payments.charge(payment)\nend)\n```\n\nInside a worker, restore the captured context before recording domain memories:\n\n```elixir\nAnimus.with_context(animus_context, fn -\u003e\n  {:ok, _} = Animus.record(:payment_worker_started, %{amount: 42})\n\n  :telemetry.execute(\n    [:my_app, :stripe, :timeout],\n    %{duration: 3_000},\n    %{service: \"stripe\", operation: \"charge\"}\n  )\n\n  {:ok, _} =\n    Animus.record(:stripe_timeout, %{service: \"stripe\", operation: \"charge\"},\n      severity: :error\n    )\nend)\n```\n\nAfter the incident:\n\n```elixir\n{:ok, explanation} = Animus.explain(request_id)\n\nexplanation.narrative\n# [\n#   \"Trace payment_flow started\",\n#   \"Http request started\",\n#   \"Process watch started\",\n#   \"Payment worker started\",\n#   \"Telemetry memory\",\n#   \"Stripe timeout\",\n#   \"Request failed\",\n#   \"Trace payment_flow finished\",\n#   \"Process exited\"\n# ]\n\nEnum.map(explanation.suspected_causes, \u0026 \u00261.memory_type)\n# [:stripe_timeout, :request_failed, :process_exited]\n```\n\nThe executable version of this scenario lives in\n[`examples/payment_incident`](examples/payment_incident).\n\n## Public API\n\nRecord domain memories:\n\n```elixir\nAnimus.record(:checkout_failed, %{cart_id: cart.id, reason: \"payment_timeout\"},\n  severity: :error\n)\n```\n\nRun a causal trace:\n\n```elixir\nAnimus.trace(\"checkout\", fn -\u003e\n  Checkout.submit(cart)\nend)\n```\n\nAttach automatic telemetry adapters:\n\n```elixir\nAnimus.attach_adapters([:phoenix, :ecto, :oban])\nAnimus.detach_adapters([:phoenix, :ecto, :oban])\n```\n\nBound adapter capture during production investigations:\n\n```elixir\nAnimus.attach_adapters(\n  phoenix: [sample_rate: 0.25, redact_keys: [:card_number]],\n  ecto: [sample_rate: 0.10, max_value_size: 256],\n  oban: [redact_keys: [:args]]\n)\n```\n\nAdapters redact common secret keys by default, including authorization headers,\npasswords, tokens, cookies, and API keys. Additional `:redact_keys` are merged\nwith those defaults and applied through nested maps and lists before storage.\n\nAttach custom telemetry when you need application-specific events:\n\n```elixir\nAnimus.attach_telemetry(:payments, [[:my_app, :stripe, :timeout]])\n```\n\nWatch process lifecycle transitions:\n\n```elixir\n{:ok, watch_ref} = Animus.watch(MyApp.PaymentWorker)\n:ok = Animus.unwatch(watch_ref)\n```\n\nWatch targeted mailbox pressure during an investigation:\n\n```elixir\n{:ok, watch_ref} = Animus.watch_mailbox(worker, threshold: 500, interval_ms: 1_000)\n:ok = Animus.unwatch(watch_ref)\n```\n\nPause and resume capture during an investigation:\n\n```elixir\nAnimus.disable()\nAnimus.enable()\nAnimus.enabled?()\n```\n\nQuery operational memory:\n\n```elixir\nAnimus.timeline(trace_id)\nAnimus.causal_tree(trace_id)\nAnimus.explain(trace_id)\nAnimus.inspect_crash(trace_id)\nAnimus.bleeding_effect(trace_id)\n```\n\nCrash inspection gives a focused failure view:\n\n```elixir\n{:ok, inspection} = Animus.inspect_crash(request_id)\n\ninspection.crash.memory_type\n# :process_exited\n\nEnum.map(inspection.suspected_causes, \u0026 \u00261.memory_type)\n# [:stripe_timeout, :request_failed]\n\ninspection.story\n# \"Failure point: Process exited. Suspected causes before the failure: Stripe timeout, Request failed.\"\n```\n\nThe bleeding effect is what remains after leaving the Animus: the trace is over,\nbut the system carries forward what it learned. It is a deterministic summary of\nstats, patterns, suspected causes, and the same crash story without involving AI.\n\n```text\nThe incident has ended.\nThe memory has not.\nWhat the system lived through becomes what the engineer can know.\n```\n\n```elixir\n{:ok, effect} = Animus.bleeding_effect(request_id)\n\neffect.memory_type_counts[\"mailbox_warning\"]\n# 1\n\nEnum.map(effect.patterns, \u0026 \u00261.type)\n# [:mailbox_pressure_before_crash, :external_timeout_before_crash, :process_crash]\n```\n\nTime-travel by incident window:\n\n```elixir\nAnimus.memories_on(~D[2026-05-14])\n\nAnimus.memories_between(\n  ~U[2026-05-14 13:00:00Z],\n  ~U[2026-05-14 14:00:00Z]\n)\n```\n\n## Installation\n\nInstall the host-app migration:\n\n```sh\nmix animus.install\nmix ecto.migrate\n```\n\nAnimus stores memories in `animus_memories`, not a generic `memories` table.\n\nRetention defaults to 30 days and is configurable:\n\n```elixir\nconfig :animus,\n  repo: MyApp.Repo,\n  enabled: true,\n  retention_days: 30,\n  retention_interval_ms: :timer.hours(1)\n```\n\nAnimus uses the host application repo. It does not start or own a database\nconnection pool.\n\nUse `enabled: false` if you want instrumentation installed but capture paused\nuntil an investigation starts.\n\n## Operator Telemetry\n\nAnimus emits its own telemetry when capture degrades or changes payloads before\nstorage. Attach these events if you want alerts or counters for Animus itself:\n\n```elixir\n[\n  [:animus, :memory, :dropped],\n  [:animus, :memory, :sink_failed],\n  [:animus, :adapter, :sampled_out],\n  [:animus, :adapter, :payload_redacted],\n  [:animus, :adapter, :payload_truncated]\n]\n```\n\nThese events include counts and safe metadata such as `:memory_type`, `:source`,\n`:adapter`, `:event_name`, `:reason`, and redacted key names. They do not include\nmemory payload values.\n\n## Safety Defaults\n\nAnimus is designed for incident reconstruction, not unbounded tracing.\n\n- Uses the host application repo; no separate database pool.\n- Stores memories in `animus_memories`.\n- Keeps a bounded retention window by default.\n- Captures common framework telemetry through explicit adapters.\n- Lets adapters sample, redact sensitive keys, and truncate oversized values.\n- Keeps trace and watcher capture best-effort so sink failures do not block app code.\n- Emits operator telemetry when capture is dropped, sampled, redacted, truncated, or sink writes fail.\n- Watches only the processes and mailboxes you choose.\n- Does not globally trace BEAM messages.\n- Does not use AI to invent causes.\n\n## Current Capabilities\n\nAnimus currently provides:\n\n- Memory ingestion\n- Telemetry integration\n- Trace context propagation\n- Process lifecycle monitoring\n- Targeted mailbox pressure monitoring\n- Timeline reconstruction\n- Causal explanations and crash inspection\n- Bleeding effect summaries for stats and patterns\n- Lightweight API\n- Host-repo Postgres persistence\n- Configurable retention\n\nAnimus does not currently provide:\n\n- AI anomaly detection\n- Full BEAM message tracing\n- Distributed replay\n- Machine learning\n- Predictive analysis\n- Fancy dashboards\n- Multi-language support\n\nSee [Current capabilities](docs/v1-scope.md) for the library boundary.\n\n## Development\n\n```sh\nmix precommit\nmix examples.smoke\n```\n\nThe payment incident example is executable and runs as part of `mix precommit`.\n\n## License\n\nApache-2.0. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fccarvalho-eng%2Fanimus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fccarvalho-eng%2Fanimus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fccarvalho-eng%2Fanimus/lists"}