Observability & Production Ops: Trace, Debugging & Governance
메뉴

AI Agent Engineering

Observability & Production Ops: Trace, Debugging & Governance

OpenTelemetry Distributed Tracing, Loop Debugging, Infinite Loop/Hallucination Detection, and Governance.

Observability & Production Ops: Trace, Debugging & Governance hero image

This post is Part 7 of the AI Agent Engineering series. It systematically covers observability, debugging, incident response, governance, and operational pipelines for AI agents executing nondeterministic and complex autonomous loops in production environments.


1. The Observability Paradigm Shift for Production AI Agents

In traditional web applications or single-prompt LLM API calls (Single Prompt Execution), the relationship between input and output is relatively intuitive. When an HTTP request arrives, the backend queries a database or executes static business logic to return a predictable response. Standard Application Performance Monitoring (APM) tools focusing on Latency, Throughput, and Error Rates are sufficient for detecting system anomalies.

However, with the adoption of autonomous AI agents, the observability paradigm has fundamentally shifted.

1.1 Comparison of Observability Characteristics by System Type

Metric CategoryTraditional Web APISingle-turn LLM CallAutonomous AI Agent
Execution PathStatic & DeterministicSingle I/O (Prompt → Completion)Nondeterministic Dynamic Loop
State ManagementDB / Session-based Static StateStatelessPer-iteration State Delta & Memory Store
Core Observability MetricsLatency, Throughput, HTTP 5xx RateToken Count, Time to First Token (TTFT)Span Hierarchy, Tool Call Accuracy, Step Count
Debugging ApproachStack Trace, APM LogInput/Output Prompt Pair LogCheckpoint Replay, Time-travel State Inspection
Primary Failure ModesNull Pointer Exception, DB TimeoutHallucination, Format ErrorInfinite Loop, Cascading Sub-agent Deadlock

In an agent runtime environment, unique engineering challenges arise:

  1. Nondeterministic Dynamic Execution Paths: Even when given the exact same goal, generated plans and tool invocation sequences can vary significantly based on environment state, LLM sampling, and external tool responses.
  2. Multi-turn Loop & State Transition: Rather than finishing in a single call, the agent iterates through $N$ or more turns, continually mutating its internal State. When a failure occurs, pinpointing "in which turn and tool call state corruption happened" is exceptionally difficult.
  3. Sub-agent Cascading Delegation: An N-tier hierarchical structure emerges where manager agents delegate tasks to worker agents, which in turn call external Model Context Protocol (MCP) servers or API tools.

The Necessity of Agent Observability Operating agents in production requires an engineering framework capable of tracking internal reasoning steps, tool input/output data, state mutation deltas, token costs, and safety guardrail triggers in real time, going far beyond simple log files or HTTP request metrics.


2. OpenTelemetry-Based Distributed Tracing & Hierarchical Span Design

The core pillar of AI agent observability is Distributed Tracing compliant with the OpenTelemetry (OTel) standard. A single full agent execution cycle forms a Root Span, while internal iterations, LLM Reasoner calls, Tool Executions, and Sub-agent calls form Child Spans with explicit Parent-Child relationships.

2.1 Span Hierarchy Tree Architecture

The agent runtime span structure is categorized into 5 primary layers:

  • Root Span (agent.run): Records the user's input goal, Session ID, Agent ID, execution start/end timestamps, and final success status.
  • Iteration Span (agent.iteration): Represents the $n$-th iteration of the agent loop, recording agent state deltas before and after entering the turn.
  • LLM Reasoner Span (llm.call): Records prompt token count, completion token count, model name used, temperature, stop sequences, and raw thought/tool calls returned by the LLM.
  • Tool Execution Span (tool.execute): Records called tool name (tool_name), arguments (tool_args), execution output (tool_output), external API latency, and exception details on failure.
  • Sub-Agent Delegation Span (subagent.run): Propagates trace context (traceparent) when delegating to sub-agents, seamlessly linking parent and child agent traces into a unified distributed trace.

2.2 OTel Semantic Conventions for Agent Execution

Defining standard span attributes following OpenTelemetry LLM/GenAI Workgroup specifications and agent engineering standards is crucial.

OpenTelemetry GenAI Semantic Conventions In the LLM and Agent Observability ecosystem, standardizing Span Attribute keys according to the OpenTelemetry GenAI Workgroup semantic conventions ensures full interoperability. This guarantees zero telemetry data loss when transitioning across APM backends or LLM observability platforms such as Datadog, Honeycomb, LangSmith, or Arize Phoenix.

Attribute NameTypeDescriptionExample
gen_ai.systemstringLLM provider or runtime nameopenai, anthropic, langgraph
gen_ai.request.modelstringFrontier model name used for requestgpt-4o, claude-3-5-sonnet
gen_ai.usage.input_tokensintPrompt tokens consumed for input1420
gen_ai.usage.output_tokensintCompletion tokens consumed for generation380
agent.run_idstringUnique identifier for a single agent execution unitrun-9a8f23
agent.iterationintCurrent agent execution loop count3
agent.tool.namestringUnique name of executed toolexecute_python_code
agent.tool.statusstringTool execution result statussuccess, error, blocked
agent.circuit_breaker.triggeredboolWhether safety guardrail tripped the circuit breakertrue, false

3. Structured Logging and Agent Loop Debugging

While distributed tracing visualizes the temporal and hierarchical flow of overall execution, Structured Logging handles fine-grained state mutation tracking and precision debugging.

3.1 Limitations of Plain Text Logging and Pydantic Event Schemas

Simply logging formatted strings like logger.info(f"Agent state: {state}") makes it difficult for log management platforms (Elasticsearch, Datadog, Loki, etc.) to query nested fields or configure automated alerts. Therefore, all agent events must be encoded and emitted using strict JSON schemas powered by Pydantic v2.

{  "timestamp": "2026-07-27T10:15:30.124Z",  "level": "INFO",  "event_type": "agent_tool_execution_completed",  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",  "span_id": "00f067aa0ba902b7",  "agent_id": "DataAnalystAgent",  "run_id": "run-9a8f23",  "iteration": 2,  "payload": {    "tool_name": "query_database",    "execution_time_ms": 142.5,    "input_params": {"query": "SELECT count(*) FROM users;"},    "output_summary": {"row_count": 1, "status": "ok"},    "state_delta": {      "added_keys": ["db_result"],      "updated_keys": ["messages"]    }  }}

3.2 Replayability Checkpoints and Deterministic Replay Mode

When errors occur in non-deterministic production agents, the hardest issue to solve is: "The bug cannot be reproduced locally even with the exact same input prompt." To resolve this, the agent runtime must operate a Checkpoint Store that persists three critical elements for each iteration:

  1. Input Snapshot: Agent State and full Message History at loop entry.
  2. LLM Response Freeze: Raw text and function call JSON returned by the LLM.
  3. Tool Execution Result Freeze: Exact values returned by external APIs/tools.

The Value of Deterministic Replayability Production failures caused by agent non-determinism are nearly impossible to reproduce from raw logs alone. By freezing and locking snapshots of State, LLM Completions, and External API Responses for every iteration in a Checkpoint Store (e.g., Redis, PostgreSQL), developers can perform step-by-step offline replay debugging without triggering live external API calls.

3.3 Debugging Approach Comparison

Debugging ApproachExternal API CallsDeterminismDebug Speed & CostIdeal Failure Analysis Use Case
Plain Text Log AnalysisNone (Log query only)Very LowFast / $0 CostSimple syntax errors, server crash detection
Live Re-run (Re-execution)Live API re-callNon-deterministic (Varying results)Slow / Incurs API costTesting non-deterministic prompt improvements
Deterministic Checkpoint ReplayNone (Mocked replay)100% DeterministicExtremely Fast / $0 CostComplex loop state corruption, edge case analysis

4. Agent Failure Analysis & Real-Time Detection/Mitigation

Building real-time automatic mitigation (Circuit Breakers) for the 4 Major Production Agent Failure Patterns is essential for operating production agents.

4.1 Comparison of 4 Major Production Agent Failure Patterns

Failure PatternRoot CauseSystem ImpactMitigation & Circuit Breaker
Infinite Loop / RecursionRepeatedly calling the same tool with identical arguments / failing to convergeAPI hanging, resource exhaustion, stuck loopsDuplicate Tool Call Detector
(Trips circuit breaker if identical (tool, args) is called 3 consecutive times)
Hallucinated Tool CallCalling undefined tool names / argument type mismatchExecution runtime exceptionPydantic Guardrail & Retry Prompt
(Re-sends argument correction prompt once before fail-safe fallback)
Token Storm & Cost SpikeLarge tool outputs (500KB JSON) accumulating / prolonged loopsLLM API cost spikes, Context Window overflowContext Window Pruner & Budget Cap
(Enforces cumulative token budget limit & history summarization)
Cascading Sub-agent DeadlockUnhandled worker timeouts, cyclic delegation (Agent A → B → A)System-wide deadlock (Thread starvation)Cascading Timeout & Cycle Detector
(Delegation depth limiting & timeout propagation)

4.2 Circuit Breaker State Transition Architecture

Inside the agent runtime loop, Safety Middleware operating independently of LLM decisions is required to forcibly terminate execution or hand over control to Human-in-the-Loop workflows.

Key Guardrail Mechanisms:

  • Max Iteration Limit: Enforces maximum loop counts (e.g., immediate abort if exceeding 10 turns).
  • Duplicate Tool Call Detector: Aborts loop if identical (tool_name, tool_args) combinations repeat 3 or more times within recent $N$ iterations.
  • Context Window Pruner / Token Budget Cap: Blocks execution if cumulative token consumption per run exceeds designated budget (e.g., 100,000 Tokens).
  • Schema Validation Guardrail: Validates LLM tool call outputs using Pydantic. On validation failure, re-sends an argument correction prompt once; on second failure, gracefully returns an error.

Preventing API Cost Explosions from Unchecked Loops Infinite loops triggered by repeatedly invoking tools with identical arguments or retrying hallucinated tools can consume millions of tokens and trigger excessive external API queries within minutes. Circuit breakers are not optional features; they are an indispensable safety kill-switch for production runtimes.


5. Versioning, Regression Testing, Deployment & Governance

Agent software is a composite system combining standard application code with System Prompts, Tool Specifications, and LLM Model Versions.

5.1 Agent Specification Versioning Standard

When deploying agents, an explicit Agent Spec Version combining three core components must be maintained:

Agent Spec Version = [Code Release] + [Prompt Revision] + [Tool Specification Hash]Example: v2.4.0-p12.a9b1c3
ComponentIdentifier FormatMetadata IncludedImpact & Action
Code ReleasevX.Y.Z (SemVer)Agent runtime, orchestration engine, circuit breaker codeFull CI/CD test suite execution on Major/Minor updates
Prompt Revisionp1, p2, ...System Instruction, Few-shot Examples, Task ConstraintsMust pass Golden Evaluation Suite when prompts change
Tool Spec Hasha9b1c3 (SHA-256)MCP Server specification, Pydantic Tool Schema, API EndpointsAPI compatibility regression testing on Tool Signature change

If any single element changes, it constitutes a new Agent Spec Version and must undergo Regression Evaluation within the CI/CD pipeline.

5.2 CI/CD Regression Evaluation Flow

To prevent agent degradation when deploying new prompts or tools, automated evaluations against a Golden Dataset are executed.

5.3 Core Agent Evaluation Metrics

Eval MetricTarget ThresholdEvaluation MethodDescription & Validation Content
Task Completion Rate$\ge 95%$Ground Truth Output comparison / LLM-as-a-JudgeVerifies whether agent successfully completes given goal
Tool Selection Accuracy$\ge 98%$Exact Schema Matching & Parameter CheckConfirms correct tools are invoked with valid argument formats
Step Efficiency$\le 4.5$ turnsAggregate iteration count (Avg Iteration Count)Average iterations spent per goal (detects unnecessary exploration)
Token Cost Efficiency$\le $0.05$/runOTel Token Span AggregateAverage dollar cost per execution run

5.4 Security & Compliance Governance

  • Human-in-the-Loop (HITL) Approval System: Destructive actions (DB deletions, payments, external emails, etc.) transition to a WAITING_FOR_APPROVAL pending state requiring admin authorization before execution.
  • Audit Log Immutability: All observability telemetry and tool execution history are immediately synchronized to an immutable Write-Once-Read-Many (WORM) audit log store.
  • PII / Secret Masking: Automatic redaction masking is applied to sensitive data (SSNs, credit cards, API keys) prior to sending OpenTelemetry spans and log events to exporters.

PII Protection and Destructive Action Governance LLM prompts and completions captured in traces and logs frequently contain sensitive customer data (SSNs, API keys, financial records). Passing telemetry through a PII Redaction Engine upstream of OTel Exporters is mandatory. Additionally, destructive tool calls (data deletion, payment execution) must transition into a WAITING_FOR_APPROVAL state to enforce Human-in-the-Loop approval.


6. Python Implementation: OpenTelemetry Agent Observability Runtime Core

The Python snippet below demonstrates a production-grade runtime combining OpenTelemetry SDK, Pydantic v2, and asyncio to provide hierarchical tracing, infinite loop circuit breakers, and structured logging.

"""AI Agent Observability & Circuit Breaker Runtime Core Module(OpenTelemetry Distributed Tracing + Pydantic v2 + Safety Guardrails)""" import asyncioimport jsonimport loggingimport timefrom typing import Any, Dict, List, Optional, Tuplefrom pydantic import BaseModel, Field # OpenTelemetry Modulesfrom opentelemetry import tracefrom opentelemetry.trace import Status, StatusCode, SpanKindfrom opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter # OTel Tracer Setupprovider = TracerProvider()processor = BatchSpanProcessor(ConsoleSpanExporter())provider.add_span_processor(processor)trace.set_tracer_provider(provider) tracer = trace.get_tracer("agent.observability.core", "1.0.0")logger = logging.getLogger("AgentLogger")logging.basicConfig(level=logging.INFO, format="%(message)s") # ---------------------------------------------------------------------------# 1. Pydantic Schema Definitions# --------------------------------------------------------------------------- class ToolCallRequest(BaseModel):    tool_name: str = Field(description="Name of the tool to invoke")    tool_args: Dict[str, Any] = Field(default_factory=dict, description="Tool input parameters") class AgentState(BaseModel):    goal: str    messages: List[Dict[str, Any]] = Field(default_factory=list)    context_data: Dict[str, Any] = Field(default_factory=dict)    iteration_count: int = 0    is_completed: bool = False class AgentExecutionConfig(BaseModel):    max_iterations: int = 5    max_duplicate_tool_calls: int = 2    max_token_budget: int = 50000 class StructuredLogEvent(BaseModel):    """Structured event log schema powered by Pydantic v2"""    timestamp: str = Field(default_factory=lambda: time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()))    event_type: str    trace_id: Optional[str] = None    span_id: Optional[str] = None    agent_id: str    run_id: str    iteration: int    payload: Dict[str, Any] = Field(default_factory=dict) # ---------------------------------------------------------------------------# 2. Circuit Breaker & Guardrail Classes# --------------------------------------------------------------------------- class AgentCircuitBreaker:    """Guardrail that detects infinite loops and agent failure patterns to abort execution"""     def __init__(self, config: AgentExecutionConfig):        self.config = config        self.tool_call_history: List[str] = []        self.total_tokens_used: int = 0     def check_guardrails(self, state: AgentState, pending_tool: Optional[ToolCallRequest] = None) -> None:        # 1. Max iteration limit check (abort if max_iterations exceeded)        if state.iteration_count > self.config.max_iterations:            raise RuntimeError(f"[CircuitBreaker] Exceeded max iterations ({self.config.max_iterations}). Forcibly terminating execution.")         # 2. Token budget check        if self.total_tokens_used >= self.config.max_token_budget:            raise RuntimeError(f"[CircuitBreaker] Exceeded allocated token budget ({self.config.max_token_budget}).")         # 3. Duplicate tool call check (Infinite Loop Detector)        if pending_tool:            tool_signature = f"{pending_tool.tool_name}:{json.dumps(pending_tool.tool_args, sort_keys=True)}"            same_calls = [sig for sig in self.tool_call_history if sig == tool_signature]            if len(same_calls) >= self.config.max_duplicate_tool_calls:                raise RuntimeError(                    f"[CircuitBreaker] Repeated tool invocation detected ({pending_tool.tool_name}). Aborting execution."                )            self.tool_call_history.append(tool_signature)     def record_tokens(self, tokens: int) -> None:        self.total_tokens_used += tokens # ---------------------------------------------------------------------------# 3. Observability Agent Runtime Engine# --------------------------------------------------------------------------- class ObservabilityAgentRuntime:    def __init__(self, agent_id: str, config: AgentExecutionConfig):        self.agent_id = agent_id        self.config = config     async def execute_run(self, run_id: str, goal: str) -> AgentState:        """Generates Root Run Span and executes agent main loop"""        state = AgentState(goal=goal)        circuit_breaker = AgentCircuitBreaker(self.config)         # 1. Create Root Span: agent.run        with tracer.start_as_current_span(            name="agent.run",            kind=SpanKind.SERVER,            attributes={                "agent.id": self.agent_id,                "agent.run_id": run_id,                "agent.goal": goal,            }        ) as root_span:            self._log_structured_event("agent_run_started", run_id, 0, {"goal": goal})             try:                while not state.is_completed:                    state.iteration_count += 1                     # 2. Create Child Span: agent.iteration                    with tracer.start_as_current_span(                        name=f"agent.iteration_{state.iteration_count}",                        attributes={"agent.iteration": state.iteration_count}                    ) as iter_span:                        # Check guardrail state                        circuit_breaker.check_guardrails(state)                         # Execute LLM reasoning step                        llm_decision, tokens_used = await self._step_llm_reasoner(run_id, state)                        circuit_breaker.record_tokens(tokens_used)                         # If tool call requested                        if llm_decision.get("type") == "tool_call":                            tool_req = ToolCallRequest(**llm_decision["payload"])                                                        # Re-verify guardrails before tool execution                            circuit_breaker.check_guardrails(state, pending_tool=tool_req)                             # Execute tool                            tool_result = await self._step_execute_tool(run_id, state.iteration_count, tool_req)                            state.messages.append({"role": "tool", "content": tool_result})                         elif llm_decision.get("type") == "finish":                            state.is_completed = True                            state.context_data["final_answer"] = llm_decision["payload"]["answer"]                            iter_span.set_attribute("agent.status", "completed")             except Exception as exc:                root_span.record_exception(exc)                root_span.set_status(Status(StatusCode.ERROR, str(exc)))                self._log_structured_event("agent_run_failed", run_id, state.iteration_count, {"error": str(exc)})                raise exc             root_span.set_status(Status(StatusCode.OK))            self._log_structured_event("agent_run_completed", run_id, state.iteration_count, {                "total_tokens": circuit_breaker.total_tokens_used,                "final_answer": state.context_data.get("final_answer")            })            return state     async def _step_llm_reasoner(self, run_id: str, state: AgentState) -> Tuple[Dict[str, Any], int]:        """LLM reasoning span and token usage aggregation"""        with tracer.start_as_current_span("llm.call") as span:            start_time = time.time()             # Simulated LLM processing            await asyncio.sleep(0.1)            prompt_tokens = 1500 + (state.iteration_count * 200)            completion_tokens = 150            total_tokens = prompt_tokens + completion_tokens             # Record OTel Attributes            span.set_attribute("gen_ai.system", "openai")            span.set_attribute("gen_ai.request.model", "gpt-4o")            span.set_attribute("gen_ai.usage.input_tokens", prompt_tokens)            span.set_attribute("gen_ai.usage.output_tokens", completion_tokens)             # Simulated reasoning decision (completes on turn 3 after 2 tool calls)            if state.iteration_count < 3:                decision = {                    "type": "tool_call",                    "payload": {                        "tool_name": "fetch_user_analytics",                        "tool_args": {"user_id": "usr_9912", "period": "30d"}                    }                }            else:                decision = {                    "type": "finish",                    "payload": {"answer": "User analytics collection successfully completed."}                }             latency_ms = (time.time() - start_time) * 1000            self._log_structured_event("llm_call_completed", run_id, state.iteration_count, {                "prompt_tokens": prompt_tokens,                "completion_tokens": completion_tokens,                "latency_ms": latency_ms            })            return decision, total_tokens     async def _step_execute_tool(self, run_id: str, iteration: int, req: ToolCallRequest) -> str:        """Tool execution span recording and async execution"""        with tracer.start_as_current_span(f"tool.execute:{req.tool_name}") as span:            span.set_attribute("agent.tool.name", req.tool_name)            span.set_attribute("agent.tool.args", json.dumps(req.tool_args))             start_time = time.time()            try:                # Simulated tool execution                await asyncio.sleep(0.05)                output = f"Result for {req.tool_name} with args {req.tool_args}"                 span.set_attribute("agent.tool.status", "success")                latency_ms = (time.time() - start_time) * 1000                 self._log_structured_event("tool_execution_completed", run_id, iteration, {                    "tool_name": req.tool_name,                    "latency_ms": latency_ms,                    "status": "success"                })                return output            except Exception as e:                span.record_exception(e)                span.set_status(Status(StatusCode.ERROR, str(e)))                raise e     def _log_structured_event(self, event_type: str, run_id: str, iteration: int, payload: Dict[str, Any]) -> None:        """JSON structured log encoding and emission powered by Pydantic v2"""        current_span = trace.get_current_span()        ctx = current_span.get_span_context()         event = StructuredLogEvent(            event_type=event_type,            trace_id=format(ctx.trace_id, "032x") if ctx.is_valid else None,            span_id=format(ctx.span_id, "016x") if ctx.is_valid else None,            agent_id=self.agent_id,            run_id=run_id,            iteration=iteration,            payload=payload        )        logger.info(event.model_dump_json()) # ---------------------------------------------------------------------------# 4. Entrypoint Test# --------------------------------------------------------------------------- async def main():    agent_config = AgentExecutionConfig(max_iterations=5, max_duplicate_tool_calls=2)    runtime = ObservabilityAgentRuntime(agent_id="AnalyticsAgent_v1", config=agent_config)     print("=== [AI Agent Observability Runtime Test] ===")    result_state = await runtime.execute_run(run_id="run-test-1002", goal="Analyze recent 30-day data for user usr_9912")    print(f"\n[Final Execution Result]: {result_state.context_data.get('final_answer')}")     provider.shutdown() if __name__ == "__main__":    asyncio.run(main())

7. Production Operations Checklist

A comprehensive 4-domain operational checklist that engineering teams must review prior to deploying production agents.

DomainItemValidation DetailsStatus
Tracing & TelemetryOpenTelemetry Hierarchical SpansAre Parent-Child relationships between Root (agent.run), Iteration, LLM, and Tool Spans correctly formed?[ ]
GenAI OTel AttributesAre standard fields like gen_ai.system, input_tokens, output_tokens, and tool.name captured without omission?[ ]
Cross-agent Context PropagationIs context propagated via Traceparent headers during sub-agent delegation to unify traces?[ ]
Debugging & ReplayStructured JSON LoggingAre JSON event logs containing Trace ID and Span ID transmitted to central log aggregators?[ ]
Checkpoint Store SetupAre per-iteration input State, LLM completions, and Tool outputs stored to enable offline Replay?[ ]
Guardrails & SafetyMax Iteration Circuit BreakerDoes the runtime automatically terminate when loop iteration limits are breached?[ ]
Duplicate Tool DetectorAre non-convergent, repeated tool calls with identical arguments detected and halted immediately?[ ]
Token & Cost Budget CapAre infrastructure-level kill-switches triggered when token thresholds per run or account are exceeded?[ ]
Governance & DeployAgent VersioningAre Code, Prompt Revision, and Tool Spec Hash tracked as a unified specification version?[ ]
Golden Eval SuiteIs automated regression evaluation performed in CI/CD, blocking deployment if benchmark scores fall short?[ ]
HITL & PII MaskingAre pre-approval mechanisms for risky tools and sensitive data (PII) redaction enabled?[ ]

8. Conclusion: Observable Agents Are Trustworthy Agents

Promoting AI agents from simple proof-of-concept (PoC) stages to enterprise production systems ultimately hinges on Observability and Safety Guardrails. Even in nondeterministic LLM-based systems, visualizing every loop and reasoning step via OpenTelemetry distributed tracing, combined with Pydantic-based structured data capture and time-travel replay debugging, empowers engineering teams to rapidly diagnose and control incidents. Build a production-grade, reliable agent runtime by leveraging the hierarchical tracing architecture, failure pattern circuit breakers, governance versioning strategies, and operational checklists detailed in this post.

댓글

GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.

TOP