{"slug": "building-reefwatch-a-coral-powered-production-triage-agent", "title": "Building ReefWatch, a Coral-Powered Production Triage Agent", "summary": "A developer built ReefWatch, a Coral-powered production triage agent that discovers connected tools at runtime, queries them with read-only SQL, and generates incident reports only when the evidence supports a conclusion. The agent uses Coral as a data plane to abstract away individual tool SDKs, allowing it to investigate across systems like GitHub, Sentry, and Slack through a unified SQL interface. ReefWatch streams the evidence trail and produces operator-grade answers grounded in real system data rather than generic suggestions.", "body_md": "Production incidents almost never break in one place.\n\nThe alert fires in one tool. The broken deploy is in Netlify. The suspicious\n\nchange is in GitHub. The stack trace is in Sentry. The human context is in\n\nSlack. The runbook is in Notion. The \"is this actually paging someone?\" answer\n\nis in PagerDuty.\n\nA normal chatbot can sound helpful in that situation. It can say things like\n\n\"you should check your recent deployments\" and \"look for related errors in\n\nSentry.\"\n\n**But that is not triage. That is a polished to-do list.**\n\nI wanted something more useful: an agent that could go get the evidence, connect\n\nthe dots across sources, show its work, and give an operator-grade answer\n\ngrounded in real system data.\n\n*The design constraint from the start was simple: no evidence, no answer.*\n\nThat became [ ReefWatch](https://github.com/siiddhantt/reefwatch), a Coral-powered production triage agent built to\n\nIt discovers the tools connected to a workspace at runtime, queries them as\n\nevidence, correlates records across systems, and produces a compact answer only\n\nwhen the facts support one.\n\nCoral became the backbone because it turns the messiest part of agent tooling\n\ninto something the model can actually reason about: **SQL**.\n\nBy the end of this route, you will have a blueprint for an agent that can:\n\nIn one sentence:\n\nReefWatch is a Coral-powered investigation workspace that lets an agent discover connected tools at runtime, query them with read-only SQL, stream the evidence trail, and generate an incident report only when the facts actually support one.\n\nMCP is excellent as an integration layer. It gives models a way to call tools\n\nwith schemas instead of scraping humans through UI glue.\n\nBut if every source becomes a separate collection of bespoke tools, a new\n\nproblem appears:\n\nCoral changes the abstraction.\n\nIt still uses MCP, but the agent mostly sees a small set of stable capabilities:\n\ndiscover catalog, inspect schema, read the guide, and run SQL.\n\nThat means a new source is not:\n\n```\nteach ReefWatch another SDK\n```\n\nIt becomes:\n\n``` php\ninstall a Coral source -> discover the tables -> query evidence with SQL\n```\n\nThe practical win is boring in the best way: **ReefWatch can stay small.**\n\nThe app does not own GitHub pagination, Sentry auth, Slack table shapes, or\n\nNetlify deploy schemas.\n\n**Coral owns that. ReefWatch owns the investigation behavior.**\n\nThat split also maps well to how I think reliable agents should be built:\n\nground the model in real environment feedback, keep tools composable, trace the\n\nwork, and wrap the loop with small guardrails instead of hoping one perfect\n\nprompt behaves forever.\n\nMCP gives the agent hands. Coral gives it a map and a query language.\n\nIf I were rebuilding ReefWatch from scratch, I would not start with the UI.\n\nI would start with the investigation pipeline and make each layer earn its\n\nplace.\n\nRemember, it is tempting to start with the surface, but you should make the surface reflect a system that was already worth trusting.\n\nThe project came together in eight slices:\n\n| Slice | What I Built | Why It Mattered |\n|---|---|---|\n| 1 | Coral MCP client | Proved Coral could be the data plane |\n| 2 | Warm Coral session | Removed repeated MCP startup cost |\n| 3 | Schema context | Kept prompts aligned with live Coral metadata |\n| 4 | Minimal agent loop | Exposed the real model failure modes |\n| 5 | Policy modules | Made the agent reliable without hardcoding a demo |\n| 6 | Persistence | Made runs debuggable and conversations durable |\n| 7 | Streaming UI | Made the investigation inspectable |\n| 8 | Source profiles | Made setup reproducible without requiring every token |\n\nThe final project shape looks roughly like this:\n\n(a FastAPI backend, with SQLite store and React frontend)\n\n```\nreefwatch/\n|-- src/\n|   |-- api/\n|   |   |-- routes/\n|   |   |   |-- coral.py              # Coral health and source setup\n|   |   |   |-- conversations.py      # persisted investigation threads\n|   |   |   |-- investigations.py     # REST + SSE investigation runs\n|   |   |   `-- schema.py             # schema visibility for the UI\n|   |   |-- dependencies.py           # shared Settings, store, agent, session\n|   |   |-- mappers.py                # domain models to API responses\n|   |   `-- schemas.py                # API contracts\n|   `-- app/\n|       |-- adapters/\n|       |   |-- coral_session.py      # long-lived Coral process + warm cache\n|       |   |-- mcp_client.py         # JSON-RPC over coral mcp-stdio\n|       |   `-- store.py              # SQLite run/conversation persistence\n|       |-- agent/\n|       |   |-- context.py            # conversation compression\n|       |   |-- coverage.py           # evidence lane policy\n|       |   |-- events.py             # streamable trace event contracts\n|       |   |-- guardrails.py         # evidence-first retries\n|       |   |-- intent.py             # structured artifact routing\n|       |   |-- execution_policy.py   # duplicate and SQL-shape hygiene\n|       |   |-- loop.py               # LLM/tool loop\n|       |   |-- policy.py             # budgets and finalization\n|       |   |-- prompts.py            # schema-aware operating contract\n|       |   |-- schema.py             # Coral table/column context builder\n|       |   |-- source_guidance.py    # compact source idiom hints\n|       |   |-- taxonomy.py           # source lanes and shared intent vocabulary\n|       |   |-- workflow.py           # coverage, correlation, and stop checkpoints\n|       |   `-- synthesis.py          # optional incident report synthesis\n|       |-- config.py                 # centralized runtime knobs\n|       |-- coral_setup.py            # install/test Coral source profiles\n|       `-- source_profiles.py        # triage, demo, enterprise profiles\n`-- frontend/\n    `-- src/\n        |-- components/chat/          # chat surface, markdown, evidence trail\n        |-- store/                    # conversation/run state\n        `-- api/                      # backend client\n```\n\nThat structure came from the order of problems I solved.\n\nThe first backend slice was deliberately small.\n\nI wanted to answer one question:\n\n**Can ReefWatch treat Coral as the source of operational truth?**\n\nThe first proof was:\n\n`coral mcp-stdio`\n\n.`coral://tables`\n\n.`sql`\n\ntool.At that point, ReefWatch was not an agent yet. It was a thin Coral client.\n\nThat was useful because it proved the most important bet: **the app could treat\nCoral as the data plane** instead of building direct SDK integrations for\n\nThe first reusable module was `mcp_client.py`\n\n.\n\nIt owns the boring but essential transport details:\n\n`coral://guide`\n\nand `coral://tables`\n\nDesign decision:keep transport boring. Once`mcp_client.py`\n\nworked, the\n\nrest of the app could stop thinking about processes and start thinking about\n\ninvestigations.\n\nThe naive approach would be:\n\n``` php\nuser asks question -> spawn Coral -> discover schema -> ask model -> run SQL\n```\n\nThat is fine for a script.\n\nIt feels rough in a **product**.\n\nSo the second slice was `coral_session.py`\n\n. It keeps one Coral process alive,\n\nwarms the schema/guide/tool cache, and recreates the process if it dies.\n\nThat gave ReefWatch a cleaner runtime shape:\n\n``` php\napp starts -> warm Coral once -> investigations reuse the session\n```\n\nThe session cache stores three things:\n\nThat one decision made the product feel different.\n\nInstead of every user prompt waiting on MCP bootstrapping and catalog discovery,\n\n**ReefWatch starts from a warm map of the available sources.**\n\nThere is still a fallback path. If the process dies, `CoralSession`\n\ncan recreate\n\nthe client and warm the cache again.\n\nThe MCP client reads and writes JSON-RPC over stdio with UTF-8 decoding, drains\n\nstderr on a background thread, and reports useful transport errors rather than\n\nhanging silently.\n\nA production triage agent that randomly waits on its own plumbing is not a production triage agent.\n\nHardcoding source schemas would defeat the point of using Coral.\n\n**The agent must discover what is installed right now.**\n\nThe temptation was to write a hand-authored prompt like:\n\n```\nGitHub has these tables. Sentry has these tables. Slack uses channel ids.\n```\n\nThat would have made ReefWatch brittle and less Coral-native.\n\n*This was one of those small choices where the architecture either respects the\ntool it is built on, or quietly works around it.*\n\nInstead, ReefWatch builds its prompt context from Coral itself.\n\nIt reads `coral://tables`\n\n, enriches the result with `coral.columns`\n\n, groups\n\ntables by source, and includes only a compact slice of each source in the\n\nprompt.\n\n```\nSELECT schema_name, table_name, column_name, data_type\nFROM coral.columns\nORDER BY schema_name, table_name, ordinal_position\n```\n\nThe key idea:the model gets a map, not a maze.\n\nIf the source catalog is small, the model sees most of it.\n\nIf the catalog is large, the model gets enough to start and can use Coral\n\ndiscovery tools for the rest.\n\nThat keeps the prompt useful without pretending the app has permanent knowledge of every source.\n\nOnly after Coral transport and schema context worked did I build the agent loop.\n\nThe first version of `agent/loop.py`\n\nhad one job:\n\n``` php\nmessages -> LLM tool call -> Coral SQL -> tool result -> final answer\n```\n\nThat version was intentionally plain.\n\nIt let me see the raw failure modes:\n\nThose failures were useful.\n\nThey showed which parts belonged in the prompt and which parts deserved\n\ncode-level policy.\n\nA bad first agent run is not wasted time if it tells you where the system needs structure.\n\nThis was the real turning point.\n\nI **stopped trying to make one heroic system prompt do everything**.\n\nInstead, I split agent behavior into focused modules:\n\n`policy.py`\n\ndecides query budgets and finalization behavior.`guardrails.py`\n\nhandles evidence-first retries and missing-source retries.`coverage.py`\n\ndecides which evidence lanes matter for a request.`workflow.py`\n\nturns coverage and correlation into small checkpoint prompts.`execution_policy.py`\n\nskips duplicate/noisy query shapes and catches table/function syntax mistakes before they hit Coral.`context.py`\n\ncompresses conversation history.`synthesis.py`\n\ndecides whether a structured report is appropriate.This was better than making one giant prompt because each module has a clear\n\nreason to exist and can be tested:\n\n| Failure Mode | Layer That Handles It |\n|---|---|\n| Agent answers without querying | `guardrails.py` |\n| Agent stops after one source | `coverage.py` |\n| Agent ignores missing evidence lanes | `workflow.py` |\n| Agent skips cross-source correlation | `workflow.py` |\n| Agent repeats query shapes | `execution_policy.py` |\n| Agent loops too long | `policy.py` |\n| Conversation gets too large | `context.py` |\n| Report appears for ordinary questions | `synthesis.py` |\n\nThe model still has agency. The code does not prescribe exact SQL for a demo\n\nscenario.\n\nThe policy layer just **keeps the model inside the kind of investigation a\nhuman operator would expect.**\n\nThe next slice was persistence.\n\nI started with SQLite because this is a proof-of-concept and local operator\n\ntool, not a multi-tenant SaaS backend.\n\nThe important part was not Postgres. The important part was recording:\n\nThat made debugging dramatically easier.\n\nWhen a run looked bad, I could inspect the exact queries and decide whether the\n\nfailure was **prompt, policy, schema, model, or source setup**.\n\nThis is also why the frontend can hydrate conversations and show evidence\n\ninstead of keeping everything only in Redux memory.\n\nOnly then did the chat UI become valuable.\n\nThe UI was not designed as **\"talk to an AI.\"**\n\nIt was designed as an **investigation workspace**:\n\nThat UI decision matters because Coral is visualizable.\n\nThe user can see source counts, SQL queries, row counts, and the final\n\nsynthesis.\n\nReefWatch shows the route instead of hiding it behind one polished\n\nparagraph.\n\nThe last piece was source profiles.\n\nI did not want the default setup to require every possible token. That creates a\n\nbad demo path.\n\nInstead, ReefWatch has profiles:\n\n| Profile | Sources | Use Case |\n|---|---|---|\n`triage` |\nGitHub, Sentry, Slack, Netlify | lightweight production triage |\n`demo` |\ntriage + PagerDuty | richer incident response demo |\n`security` |\nGitHub, Slack, Notion, OSV | compliance/security route |\n`enterprise` |\ndemo + Notion + OSV | default hackathon showcase |\n`observability` |\ndemo + Datadog + StatusGator | deeper ops setup |\n\nThis keeps the build reproducible.\n\nA reader can start with `triage`\n\n, get a real agent working, then add\n\nNotion/OSV/PagerDuty when they want a stronger story.\n\nThe main loop is intentionally simple:\n\nThe important part is not that the loop is complicated.\n\nIt is that the loop is surrounded by small pieces of judgment.\n\nIf the user asks an operational question like \"what issues are on my GitHub?\"\n\nand the model tries to answer without querying Coral, ReefWatch injects a retry\n\nmessage:\n\n*\"You have not queried Coral yet. Do not answer with table recommendations or ask\nfor repo/org names until you first run metadata/source SQL queries to infer them.\"\"*\n\nThis fixed the first embarrassing failure mode: the agent giving me instructions\n\ninstead of doing the investigation.\n\nFor production triage, one source is almost never enough.\n\nReefWatch treats sources as evidence lanes:\n\n| Category | Sources |\n|---|---|\n| Ops | GitHub, Sentry, Netlify, Slack, PagerDuty, StatusGator |\n| Knowledge | Notion |\n| Security | GitHub, OSV, Notion, Slack |\n| Observability | Datadog |\n\nThe policy does not say \"always query everything.\"\n\nIt checks what is actually installed and what the user asked. If the user asks\n\nspecifically about GitHub, the coverage stays GitHub-scoped. If the user asks\n\nfor production triage, the agent should cover the available ops lanes before\n\nfinalizing.\n\n*You have only checked GitHub, but Sentry and Netlify are available, so prefer those lanes next.*\n\nThat is the kind of judgment I wanted outside the model.\n\nThe important refinement: coverage is a **guide, not a cage**.\n\nIf the model just discovered the right Sentry project ID or hit a column error,\n\nit is allowed to inspect Coral metadata and correct that source query before\n\nmoving on. That matters because real triage has tiny detours:\n\nHard-blocking those detours made the agent worse. ReefWatch now nudges the\n\ninvestigation path without preventing useful schema correction.\n\nThe source lane definitions and shared intent vocabulary live in `taxonomy.py`\n\n.\n\nThat small file exists for a boring but important reason: **coverage, budgets,\nand intent classification should not each carry their own slightly different\ndefinition of what \"incident\" means.**\n\nThe agent is still dynamic. `taxonomy.py`\n\ndoes not contain TraceChat queries,\n\ntable names for a demo, or source-specific SQL recipes. It only describes the\n\ncategories ReefWatch can reason about:\n\nCoral still discovers the actual tables, functions, filters, and columns at\n\nruntime.\n\nThis was the final thing I tightened before the demo.\n\nOnce multiple evidence lanes return concrete anchors, ReefWatch asks for a\n\nCoral-side correlation query instead of letting the model stitch everything\n\ntogether in prose.\n\nThe preferred shape is:\n\n```\nWITH deploy AS (...),\n     errors AS (...),\n     notes AS (...)\nSELECT ...\nFROM deploy\nJOIN errors ON ...\nLEFT JOIN notes ON ...\n```\n\nor, when the relationship is time-based instead of key-based:\n\n```\nWITH deploy AS (...),\n     errors AS (...),\n     notes AS (...)\nSELECT ...\nFROM deploy\nCROSS JOIN errors\nLEFT JOIN notes ON notes.ts <= errors.first_seen\nWHERE errors.first_seen >= deploy.created_at\n```\n\nThat checkpoint is still source-agnostic. It does not say \"for TraceChat, run\n\nthis SQL.\" It says: **if the evidence exposes IDs, URLs, releases, commits,\nservice names, channel IDs, or timestamps, prove the relationship inside Coral.**\n\nIf a correlation query fails because of SQL shape, the next instruction is not\n\n\"give up.\" It is:\n\nThis made ReefWatch feel much less like a chatbot and much more like an\n\ninvestigation workbench.\n\nOne subtle failure: a model can run a query with a hallucinated timestamp column,\n\nget zero rows, and conclude \"Slack had no evidence.\"\n\n**That is bad triage.**\n\nReefWatch treats a filtered zero-row evidence query as not fully decisive until\n\nthe model relaxes the filter or inspects the schema.\n\nA broad zero-row data query can satisfy a lane. A narrow zero-row query with\n\nextra `WHERE`\n\nfilters cannot automatically close the book.\n\nThat small distinction protects against false negatives without hardcoding\n\nSlack or any other source.\n\nAnother failure mode showed up with quiet repositories.\n\nThe model would discover the correct repo, then drift into global GitHub\n\nsearches anyway.\n\nThe fix was not \"hardcode this repository\"\n\nThe fix was a **general scope policy.**\n\nIf ReefWatch has discovered a concrete `owner/repo`\n\n, and the agent keeps\n\nrunning broad GitHub searches without `repo:owner/repo`\n\n, it nudges the agent\n\nback to scoped checks.\n\nThis now lives as workflow guidance rather than a hard execution block. The\n\npoint is the same: once a concrete anchor exists, prefer scoped evidence over\n\nanother broad search, but still allow a corrective metadata query when the model\n\nneeds to fix the route.\n\nThe budget is not about limiting Coral.\n\nCoral SQL queries are cheap compared to LLM loops.\n\nThe budget is about preventing agent drift and making the product predictable.\n\nReefWatch uses different budgets by request type:\n\nWhen the budget is reached, the model must stop querying and produce the best\n\nevidence-backed answer it can, explicitly naming unknowns.\n\nThe UI is conversational, but the product is not trying to become a general chat\n\ncompanion.\n\nThe conversation flow exists for follow-up investigations:\n\nReefWatch persists conversations and runs in SQLite.\n\nFor the agent prompt, it builds a compact context from recent runs and SQL\n\nexecutions. If the message history gets too large, `ContextWindow`\n\ncompresses\n\nolder tool chatter into an execution summary and keeps the latest turns.\n\nThat gives the model continuity without stuffing every old row into the prompt.\n\nThe first version of ReefWatch used a small keyword policy to decide whether a\n\nrun should produce an incident report.\n\nThat was useful as a fallback, but it was too blunt for a real conversation.\n\nFor example:\n\n```\nWhat did it find on Slack?\n```\n\nThat follow-up might mention \"incident chatter\" or \"deploy errors\" in the\n\nanswer, but the user did not ask for a new incident report. They asked for a\n\nsource-specific explanation.\n\nThe fix was a **structured intent classifier**.\n\nAfter the evidence answer is drafted, ReefWatch asks a lightweight structured\n\nLLM step to classify the artifact:\n\n`answer_only`\n\n`incident_report`\n\n`audit_report`\n\n`follow_up`\n\nThe prompt is intentionally narrow. It classifies the **current user request**,\n\nnot random words that appear in the answer draft or previous conversation\n\ncontext.\n\nThere are still deterministic policy boundaries:\n\n`report_policy=never`\n\nalways disables reports`report_policy=always`\n\nalways enables an incident reportThis is the pattern I ended up liking most: let the model handle semantic\n\nintent, but keep product policy outside the model.\n\nNot every question deserves a report.\n\nIf I ask \"are there any open issues on my GitHub?\", an incident report would be\n\nthe wrong artifact.\n\nIf I ask \"investigate the production regression,\" a report is useful.\n\nThe intent classifier decides the artifact. Report synthesis only runs when the\n\nmode is `incident_report`\n\n.\n\nThe structured synthesizer gets only the findings, SQL summary, and sources\n\nused.\n\nIt has to stay grounded in the evidence already collected. If evidence is weak,\n\nit must lower confidence rather than invent a root cause.\n\nThe UI is the best place to watch the investigation unfold.\n\nThe CLI is the best place to prove the plumbing works.\n\nThat split matters for a production agent. Before I ask the model to connect\n\nGitHub, Sentry, Netlify, Slack, PagerDuty, Notion, and OSV into one answer, I\n\nwant a boring setup path that can validate each lane by itself.\n\nReefWatch exposes that through `reefwatch coral`\n\n:\n\n```\nuv run reefwatch coral doctor\nuv run reefwatch coral build\nuv run reefwatch coral install-profile\nuv run reefwatch coral test-source github\nuv run reefwatch coral test-source sentry\nuv run reefwatch coral test-source netlify\nuv run reefwatch coral test-source slack\nuv run reefwatch coral test-source pagerduty\nuv run reefwatch coral test-source notion\nuv run reefwatch coral sql \"SELECT * FROM pagerduty.abilities LIMIT 5\"\n```\n\nThe important detail is that the CLI does not invent another integration layer.\n\nIt uses the same Coral configuration and the same MCP transport that the agent\n\nuses. The difference is intent: the CLI is for setup, validation, and scripted\n\ninvestigations; the web workspace is for watching evidence appear and reading\n\nthe final answer.\n\nFor example, a teammate can run:\n\n```\nuv run reefwatch investigate \"Investigate the current production issue for tracechat-ledger and tell me what needs attention now.\" --trace\n```\n\nThat gives the project a second interface without splitting the product in two.\n\nHere is the practical route another developer can follow.\n\nBuild Coral locally and point ReefWatch at the binary:\n\n```\ngit clone https://github.com/withcoral/coral.git\ncd coral\ncargo build\n```\n\nThen configure ReefWatch:\n\n```\nRW_CORAL_EXECUTABLE=../coral/target/debug/coral.exe\nRW_CORAL_REPO_PATH=../coral\nRW_CORAL_CONFIG_DIR=state/coral\nRW_SOURCE_PROFILE=enterprise\n```\n\nThe LLM I went for at the time of making and testing ReefWatch was [DeepSeek v4 Pro](https://openrouter.ai/deepseek/deepseek-v4-pro) as it is quite a powerful model for agentic workflows and is very cost efficient for the amount of work it does.\n\nReefWatch supports multi-modal LLM requests for the different stages, i.e inference, the main agent loop and the synthesis, so depending on your budget and use-case you can customise it!\n\nStart with the sources that give the best incident story without too much setup:\n\nFor the security/compliance variant, add:\n\nThe important UX decision is profiles.\n\nReefWatch does not force every source into every demo. It has `triage`\n\n, `demo`\n\n,\n\n`security`\n\n, `enterprise`\n\n, and `observability`\n\nprofiles so the setup can match\n\nthe story.\n\nUse a prompt that gives the agent enough intent but not a scripted path:\n\n```\nInvestigate the current production issue for tracechat-ledger and tell me what\nneeds attention now.\n```\n\nA good run should show:\n\nQuiet repos are harder than they look.\n\nA lazy agent says \"no issues\" after one empty query. A paranoid agent runs 30\n\nsearches and still sounds unsure.\n\nThe ReefWatch answer I want is calmer:\n\n```\nI did not find an active issue for <repo name>.\n\nGitHub is checked-empty for open issues and PRs on that repository. I did not\nfind linked deployment/runtime evidence in the installed sources. No incident\nreport was generated because this looks like a quiet repository check, not an\nactive production incident.\n```\n\nThat is the product philosophy in miniature:\n\nReefWatch depends on Coral's core strengths:\n\nThe agent does not just \"call Coral once.\"\n\n**Coral is the investigation substrate.**\n\nThe code is intentionally split:\n\n| Layer | Responsibility |\n|---|---|\n| MCP adapter | JSON-RPC over Coral stdio, UTF-8 safety, guide/resources/tools |\n| Coral session | Long-lived process and warm cache |\n| Schema model | Compact source/table/column context |\n| Prompt builder | Operating contract and live schema context |\n| Agent loop | LLM/tool loop and execution recording |\n| Policy | Budgets and finalization |\n| Coverage | Evidence lane requirements and source-level completeness |\n| Workflow | Coverage, correlation, correction, and stop checkpoints |\n| Taxonomy | Shared source lanes and investigation vocabulary |\n| Guardrails | Evidence-first and missing-source retries |\n| Context | Conversation compression |\n| Synthesis | Optional structured report |\n| API | Persistence and SSE streaming |\n\nReefWatch does not hardcode \"for tracechat, query these exact tables.\"\n\nIt gives the model a source-agnostic investigation workflow, then lets Coral's\n\nlive catalog expose the actual tables, functions, filters, and source idioms.\n\nThanks for reading, if you've reached this part!\n\nMy teammate and I built ReefWatch for the Coral Hackathon. The experience taught me so much about building autonomous agents from scratch and shaping ReefWatch into a helpful tool.\n\nThe most useful thing Coral gave ReefWatch was not just another integration.\n\nIt gave the agent a way to move through operational data with a consistent\n\nmental model:\n\n``` php\ndiscover -> inspect -> query -> correlate -> report\n```\n\nThat is the difference between a chatbot that knows what tools exist and an\n\nagent that can actually investigate.\n\nReefWatch is still a proof-of-concept, but the shape feels right: Coral handles\n\nthe source layer, ReefWatch handles the investigation behavior, and the UI shows\n\nthe route clearly enough that an operator can trust or challenge the answer.\n\nThat is the kind of agent I wanted to build.\n\nNot a narrator.\n\nAn investigator.", "url": "https://wpnews.pro/news/building-reefwatch-a-coral-powered-production-triage-agent", "canonical_source": "https://dev.to/siiddhantt/building-reefwatch-a-coral-powered-production-triage-agent-23hf", "published_at": "2026-05-30 06:43:00+00:00", "updated_at": "2026-05-30 07:11:35.119973+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-infrastructure", "large-language-models", "ai-products"], "entities": ["ReefWatch", "Coral", "Netlify", "GitHub", "Sentry", "Slack", "Notion", "PagerDuty"], "alternates": {"html": "https://wpnews.pro/news/building-reefwatch-a-coral-powered-production-triage-agent", "markdown": "https://wpnews.pro/news/building-reefwatch-a-coral-powered-production-triage-agent.md", "text": "https://wpnews.pro/news/building-reefwatch-a-coral-powered-production-triage-agent.txt", "jsonld": "https://wpnews.pro/news/building-reefwatch-a-coral-powered-production-triage-agent.jsonld"}}