<a id="db-agent"></a>
# Agent·LLM·도구 PostgreSQL 객체

agent/profile/tool/MCP/skill visibility, conversation, trigger와 사용량 객체를 전수 참조한다.

> **기준** · core `db/schema.sql`은 PostgreSQL 49개 table·최종 column 합집합 520개·명시 index 97개·function 4개·trigger 22개다. startup 보조 ledger `llm_records`와 `llm_usage`가 2개 table·31개 column·5개 index를 더해 선언 총계는 51개 table·551개 column·102개 index다. `Ensure*Table` 실패는 로그 후 계속되므로 보조 ledger 존재는 core startup 보장이 아니다. Traffic은 별도 SQLite 3개 일반 table·FTS5 virtual table 1개·index 6개다. 아래 DDL은 고정 source revision의 선언이며 실제 운영 DB 적용 상태는 관찰하지 않았다.

## 객체 색인

| 객체 | 저장소 | 목적 |
|---|---|---|
| [`llm_profiles`](#table-llm-profiles) | PostgreSQL | provider/model/base URL/secret·retry/output 설정의 profile을 저장한다. |
| [`llm_profile_health`](#table-llm-profile-health) | PostgreSQL | `llm_profile_health` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`agents`](#table-agents) | PostgreSQL | agent 역할 설정, 모델 binding, turn/runtime와 trigger 실행 방식을 저장한다. |
| [`agent_prompts`](#table-agent-prompts) | PostgreSQL | `agent_prompts` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`agent_prompt_vars`](#table-agent-prompt-vars) | PostgreSQL | `agent_prompt_vars` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`mcp_servers`](#table-mcp-servers) | PostgreSQL | `mcp_servers` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`mcp_tools_cache`](#table-mcp-tools-cache) | PostgreSQL | `mcp_tools_cache` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`agent_visibility`](#table-agent-visibility) | PostgreSQL | `agent_visibility` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`agent_skill_visibility`](#table-agent-skill-visibility) | PostgreSQL | `agent_skill_visibility` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`skill_usage`](#table-skill-usage) | PostgreSQL | `skill_usage` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`tool_usage`](#table-tool-usage) | PostgreSQL | `tool_usage` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`tools`](#table-tools) | PostgreSQL | built-in/custom tool의 노출 schema, binding, 실행 설정을 저장한다. |
| [`conversations`](#table-conversations) | PostgreSQL | `conversations` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`conversation_activities`](#table-conversation-activities) | PostgreSQL | `conversation_activities` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`agent_triggers`](#table-agent-triggers) | PostgreSQL | `agent_triggers` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`scheduler_state`](#table-scheduler-state) | PostgreSQL | `scheduler_state` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`llm_records`](#table-llm-records) | PostgreSQL | `llm_records` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |
| [`llm_usage`](#table-llm-usage) | PostgreSQL | `llm_usage` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다. |

<a id="table-llm-profiles"></a>
## `llm_profiles`

provider/model/base URL/secret·retry/output 설정의 profile을 저장한다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS llm_profiles (
    id               BIGSERIAL PRIMARY KEY,
    name             TEXT NOT NULL UNIQUE,
    format           TEXT NOT NULL CHECK (format IN ('openai','anthropic','openai-responses')),
    base_url         TEXT,
    proxy            TEXT,
    model            TEXT NOT NULL,
    api_key          TEXT,
    api_key_hint     TEXT,
    rate_per_second  DOUBLE PRECISION NOT NULL DEFAULT 0,
    rate_per_minute  DOUBLE PRECISION NOT NULL DEFAULT 0,
    context_window_k INTEGER NOT NULL DEFAULT 0,
    -- 思考参数拆成两个独立字段：thinking_type=思考开关(''/disabled/enabled)，
    -- reasoning_effort=思考强度(''/low/medium/high/xhigh/max)，互不牵连。
    reasoning_effort TEXT NOT NULL DEFAULT '',
    thinking_type    TEXT NOT NULL DEFAULT '',
    is_default       BOOLEAN NOT NULL DEFAULT false,
    -- 轮询(故障转移)参数，见 docs/LLM轮询设计.md：
    --   priority     顺位，越大越先被选中；激活配置(is_default)永远排链首，与本值无关。
    --   pool_exclude true=不作为故障转移目标(仍可被 agent/任务显式绑定使用)。
    priority         INTEGER NOT NULL DEFAULT 0,
    pool_exclude     BOOLEAN NOT NULL DEFAULT false,
    -- streaming=true(默认)走流式 SSE；false 走真·非流式(stream:false，一次性 JSON)。
    streaming        BOOLEAN NOT NULL DEFAULT true,
    -- 单次回复的输出上限(token)。0=不发送该字段，由服务端默认值决定——保持既有行为。
    -- 与 context_window_k(模型总容量，仅本地用于压缩阈值)是两回事：本值会随请求发出。
    max_tokens       INTEGER NOT NULL DEFAULT 0,
    -- 输出上限用哪个请求字段名，仅对 format='openai' 生效：
    --   ''                      = max_tokens(默认，兼容绝大多数网关)
    --   'max_completion_tokens' = 新字段；OpenAI 推理模型(o 系列/GPT-5)只认它，
    --                             发 max_tokens 会被 unsupported_parameter 拒绝。
    -- anthropic(max_tokens 必填)与 openai-responses(max_output_tokens)自带字段名，不受此值影响。
    max_tokens_field TEXT NOT NULL DEFAULT '',
    -- 自定义会话头：非空时每次请求带一个该名字的 HTTP 头，头值=当前运行的 session id
    -- (chat 会话/worker 意图)。用于某些按 session-id 头做提示缓存/粘性路由的网关。''=不发送。
    session_header_key TEXT NOT NULL DEFAULT '',
    -- 重试覆盖：次数 0=用全局默认/-1=关闭/>0=该值；间隔 0=用默认指数退避/>0=固定毫秒。
    -- 三组分别对应建连重试、空响应重试、同 provider 安全窗口重试，详见下方 ALTER 处注释。
    retry_connect_attempts    INTEGER NOT NULL DEFAULT 0,
    retry_connect_interval_ms INTEGER NOT NULL DEFAULT 0,
    retry_empty_attempts      INTEGER NOT NULL DEFAULT 0,
    retry_empty_interval_ms   INTEGER NOT NULL DEFAULT 0,
    retry_stream_attempts     INTEGER NOT NULL DEFAULT 0,
    retry_stream_interval_ms  INTEGER NOT NULL DEFAULT 0,
    created_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at       TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 후속 변경·제약

- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 0;`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS pool_exclude BOOLEAN NOT NULL DEFAULT false;`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS streaming BOOLEAN NOT NULL DEFAULT true;`
- `ALTER TABLE llm_profiles DROP CONSTRAINT IF EXISTS llm_profiles_format_check;`
- `ALTER TABLE llm_profiles ADD CONSTRAINT llm_profiles_format_check CHECK (format IN ('openai','anthropic','openai-responses'));`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS max_tokens INTEGER NOT NULL DEFAULT 0;`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS max_tokens_field TEXT NOT NULL DEFAULT '';`
- `ALTER TABLE llm_profiles DROP CONSTRAINT IF EXISTS llm_profiles_max_tokens_field_check;`
- `ALTER TABLE llm_profiles ADD CONSTRAINT llm_profiles_max_tokens_field_check CHECK (max_tokens_field IN ('','max_completion_tokens'));`
- `ALTER TABLE llm_profiles DROP CONSTRAINT IF EXISTS llm_profiles_max_tokens_check;`
- `ALTER TABLE llm_profiles ADD CONSTRAINT llm_profiles_max_tokens_check CHECK (max_tokens >= 0);`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS session_header_key TEXT NOT NULL DEFAULT '';`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS retry_connect_attempts INTEGER NOT NULL DEFAULT 0;`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS retry_connect_interval_ms INTEGER NOT NULL DEFAULT 0;`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS retry_empty_attempts INTEGER NOT NULL DEFAULT 0;`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS retry_empty_interval_ms INTEGER NOT NULL DEFAULT 0;`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS retry_stream_attempts INTEGER NOT NULL DEFAULT 0;`
- `ALTER TABLE llm_profiles ADD COLUMN IF NOT EXISTS retry_stream_interval_ms INTEGER NOT NULL DEFAULT 0;`
- `ALTER TABLE llm_profiles DROP CONSTRAINT IF EXISTS llm_profiles_retry_check;`
- `ALTER TABLE llm_profiles ADD CONSTRAINT llm_profiles_retry_check CHECK ( retry_connect_attempts >= -1 AND retry_empty_attempts >= -1 AND retry_stream_attempts >= -1 AND retry_connect_interval_ms >= 0 AND retry_empty_interval_ms >= 0 AND retry_stream_interval_ms >= 0);`

### 인덱스

- `CREATE UNIQUE INDEX IF NOT EXISTS uq_llm_one_default ON llm_profiles(is_default) WHERE is_default;`

### 의미·소비·운영 경계

[`llm_profiles` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-llm-profiles)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-llm-profile-health"></a>
## `llm_profile_health`

`llm_profile_health` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS llm_profile_health (
    profile_id  BIGINT PRIMARY KEY REFERENCES llm_profiles(id) ON DELETE CASCADE,
    fails       INTEGER NOT NULL DEFAULT 0,  -- 当前连续失败次数(成功即清零)
    trips       INTEGER NOT NULL DEFAULT 0,  -- 累计熔断次数,用于冷却时间指数退避
    open_until  TIMESTAMPTZ,                 -- 冷却截止;NULL/过期 = 未熔断
    last_error  TEXT NOT NULL DEFAULT '',
    last_at     TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 의미·소비·운영 경계

[`llm_profile_health` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-llm-profile-health)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-agents"></a>
## `agents`

agent 역할 설정, 모델 binding, turn/runtime와 trigger 실행 방식을 저장한다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS agents (
    id                BIGSERIAL PRIMARY KEY,
    key               TEXT NOT NULL UNIQUE CHECK (key ~ '^[a-z][a-z0-9_]*$'),
    name              TEXT NOT NULL,
    description       TEXT,
    role              TEXT NOT NULL,
    builtin           BOOLEAN NOT NULL DEFAULT true,
    enabled           BOOLEAN NOT NULL DEFAULT true,
    llm_profile_id    BIGINT REFERENCES llm_profiles(id) ON DELETE SET NULL,
    current_prompt_id BIGINT,
    max_turns         INTEGER NOT NULL DEFAULT 0,
    run_seconds       INTEGER NOT NULL DEFAULT 1200,
    web_search        BOOLEAN NOT NULL DEFAULT false,
    interactive_shell BOOLEAN NOT NULL DEFAULT false,
    wrapup_prompt     TEXT NOT NULL DEFAULT '',
    wrapup_max_turns  INTEGER NOT NULL DEFAULT 0,
    task_timeout_wrapup_prompt    TEXT NOT NULL DEFAULT '',
    task_timeout_wrapup_max_turns INTEGER NOT NULL DEFAULT 0,
    trigger_run_mode     TEXT    NOT NULL DEFAULT 'serial'  CHECK (trigger_run_mode IN ('serial','parallel')),
    trigger_merge_mode   TEXT    NOT NULL DEFAULT 'all' CHECK (trigger_merge_mode IN ('by_task','all','none')),
    trigger_max_parallel INTEGER NOT NULL DEFAULT 5,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
    CONSTRAINT agents_role_ck CHECK (role IN ('goals','main','planner','worker','assistant'))
);
```

### 후속 변경·제약

- `ALTER TABLE agents ADD COLUMN IF NOT EXISTS trigger_run_mode TEXT NOT NULL DEFAULT 'serial';`
- `ALTER TABLE agents ADD COLUMN IF NOT EXISTS trigger_merge_mode TEXT NOT NULL DEFAULT 'all';`
- `ALTER TABLE agents ADD COLUMN IF NOT EXISTS trigger_max_parallel INTEGER NOT NULL DEFAULT 5;`
- `ALTER TABLE agents ADD COLUMN IF NOT EXISTS llm_profile_id BIGINT REFERENCES llm_profiles(id) ON DELETE SET NULL;`
- `ALTER TABLE agents ALTER COLUMN run_seconds SET DEFAULT 1200;`

Fresh `CREATE`는 `trigger_run_mode` (`serial/parallel`)와 `trigger_merge_mode` (`by_task/all/none`) CHECK를 만들지만 legacy `ADD COLUMN`은 두 CHECK를 추가하지 않는다. 따라서 upgraded DB의 mode allowlist는 backend 쓰기 검증에 의존하며 DB 전역 불변식이 아니다. `run_seconds` ALTER는 기존 row를 1200으로 backfill하지 않고 이후 default INSERT에만 적용한다. `current_prompt_id`는 두 table을 만든 후 `agent_prompts(id) ON DELETE SET NULL` FK를 조건부로 더한다. [migration 경계](functions-triggers.md#schema-migrations)

### 인덱스

- `CREATE INDEX IF NOT EXISTS idx_agents_llm_profile ON agents(llm_profile_id) WHERE llm_profile_id IS NOT NULL;`

### 의미·소비·운영 경계

[`agents` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-agents)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-agent-prompts"></a>
## `agent_prompts`

`agent_prompts` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS agent_prompts (
    id            BIGSERIAL PRIMARY KEY,
    agent_id      BIGINT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
    version       INT NOT NULL,
    template_text TEXT NOT NULL,
    note          TEXT,
    updated_by    TEXT,
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (agent_id, version)
);
```

### 의미·소비·운영 경계

[`agent_prompts` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-agent-prompts)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-agent-prompt-vars"></a>
## `agent_prompt_vars`

`agent_prompt_vars` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS agent_prompt_vars (
    id          BIGSERIAL PRIMARY KEY,
    agent_id    BIGINT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
    var_name    TEXT NOT NULL,
    description TEXT,
    example     TEXT,
    source      TEXT NOT NULL CHECK (source IN ('exploration','runtime','distilled')),
    UNIQUE (agent_id, var_name)
);
```

### 의미·소비·운영 경계

[`agent_prompt_vars` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-agent-prompt-vars)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-mcp-servers"></a>
## `mcp_servers`

`mcp_servers` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS mcp_servers (
    id          BIGSERIAL PRIMARY KEY,
    name        TEXT NOT NULL UNIQUE,
    transport   TEXT NOT NULL CHECK (transport IN ('stdio','http','sse')),
    command     TEXT,
    args        JSONB NOT NULL DEFAULT '[]',
    env         JSONB NOT NULL DEFAULT '{}',
    url         TEXT,
    enabled     BOOLEAN NOT NULL DEFAULT true,
    insecure    BOOLEAN NOT NULL DEFAULT false,  -- http: 跳过 TLS 证书校验(自签证书场景, issue #108)
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 후속 변경·제약

- `ALTER TABLE mcp_servers DROP CONSTRAINT IF EXISTS mcp_servers_transport_check;`
- `ALTER TABLE mcp_servers ADD CONSTRAINT mcp_servers_transport_check CHECK (transport IN ('stdio','http','sse'));`
- `ALTER TABLE mcp_servers ADD COLUMN IF NOT EXISTS insecure BOOLEAN NOT NULL DEFAULT false;`

Startup은 transport CHECK를 drop/recreate하므로 legacy row를 전수 재검증하고 table lock을 취할 수 있다. 또한 `ScopeSentry`라는 이름이 없을 때만 URL null, empty API-key env, disabled인 HTTP placeholder를 seed하며 기존 동명 row를 덮어쓰지 않는다. [migration 경계](functions-triggers.md#schema-migrations)

### 의미·소비·운영 경계

[`mcp_servers` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-mcp-servers)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-mcp-tools-cache"></a>
## `mcp_tools_cache`

`mcp_tools_cache` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS mcp_tools_cache (
    id            BIGSERIAL PRIMARY KEY,
    server_id     BIGINT NOT NULL REFERENCES mcp_servers(id) ON DELETE CASCADE,
    tool_name     TEXT NOT NULL,
    description   TEXT,
    schema        JSONB,
    discovered_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (server_id, tool_name)
);
```

### 의미·소비·운영 경계

[`mcp_tools_cache` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-mcp-tools-cache)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-agent-visibility"></a>
## `agent_visibility`

`agent_visibility` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS agent_visibility (
    agent_id      BIGINT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
    resource_kind TEXT   NOT NULL CHECK (resource_kind IN ('mcp')),
    resource_id   BIGINT NOT NULL,
    mcp_tool_name TEXT   NOT NULL DEFAULT '',
    enabled       BOOLEAN NOT NULL DEFAULT true,
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (agent_id, resource_kind, resource_id, mcp_tool_name)
);
```

### 인덱스

- `CREATE INDEX IF NOT EXISTS idx_vis_resource ON agent_visibility(resource_kind, resource_id);`

### 의미·소비·운영 경계

[`agent_visibility` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-agent-visibility)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-agent-skill-visibility"></a>
## `agent_skill_visibility`

`agent_skill_visibility` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS agent_skill_visibility (
    agent_id   BIGINT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
    skill_name TEXT   NOT NULL,
    enabled    BOOLEAN NOT NULL DEFAULT true,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (agent_id, skill_name)
);
```

### 인덱스

- `CREATE INDEX IF NOT EXISTS idx_askv_skill ON agent_skill_visibility(skill_name);`

### 의미·소비·운영 경계

[`agent_skill_visibility` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-agent-skill-visibility)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-skill-usage"></a>
## `skill_usage`

`skill_usage` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS skill_usage (
    id             BIGSERIAL PRIMARY KEY,
    ts             TIMESTAMPTZ NOT NULL DEFAULT now(),
    skill          TEXT NOT NULL,
    agent_key      TEXT,
    task_id        BIGINT,
    exploration_id BIGINT,
    intent_id      BIGINT,
    session_id     TEXT,
    args_len       INTEGER NOT NULL DEFAULT 0,
    -- false = 模型点名了一个不存在的 skill(未命中)。这类行同样保留：它反映"想用但没有"
    -- 的缺口，是补 skill 的依据。
    found          BOOLEAN NOT NULL DEFAULT true
);
```

### 인덱스

- `CREATE INDEX IF NOT EXISTS idx_skill_usage_skill ON skill_usage(skill, ts DESC);`
- `CREATE INDEX IF NOT EXISTS idx_skill_usage_task ON skill_usage(task_id);`

### 의미·소비·운영 경계

[`skill_usage` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-skill-usage)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-tool-usage"></a>
## `tool_usage`

`tool_usage` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS tool_usage (
    id             BIGSERIAL PRIMARY KEY,
    ts             TIMESTAMPTZ NOT NULL DEFAULT now(),
    tool_key       TEXT NOT NULL,
    agent_key      TEXT,
    task_id        BIGINT,
    exploration_id BIGINT,
    intent_id      BIGINT,
    session_id     TEXT
);
```

### 인덱스

- `CREATE INDEX IF NOT EXISTS idx_tool_usage_tool ON tool_usage(tool_key, ts DESC);`
- `CREATE INDEX IF NOT EXISTS idx_tool_usage_task ON tool_usage(task_id);`

### 의미·소비·운영 경계

[`tool_usage` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-tool-usage)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-tools"></a>
## `tools`

built-in/custom tool의 노출 schema, binding, 실행 설정을 저장한다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS tools (
    key         TEXT PRIMARY KEY,
    system      BOOLEAN NOT NULL DEFAULT true,
    description TEXT    NOT NULL DEFAULT '',
    schema      JSONB   NOT NULL DEFAULT '{}',
    agents      JSONB   NOT NULL DEFAULT '[]',
    enabled     BOOLEAN NOT NULL DEFAULT true,
    kind        TEXT    NOT NULL DEFAULT 'builtin',
    exec        JSONB   NOT NULL DEFAULT '{}',
    deferred    BOOLEAN NOT NULL DEFAULT false,
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 의미·소비·운영 경계

[`tools` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-tools)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-conversations"></a>
## `conversations`

`conversations` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS conversations (
    id             BIGSERIAL PRIMARY KEY,
    agent_key      TEXT NOT NULL,
    title          TEXT NOT NULL DEFAULT '',
    llm_profile_id BIGINT REFERENCES llm_profiles(id) ON DELETE SET NULL,
    pinned_at      TIMESTAMPTZ,
    created_at     TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at     TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 후속 변경·제약

- `ALTER TABLE conversations ADD COLUMN IF NOT EXISTS pinned_at TIMESTAMPTZ;`

### 인덱스

- `CREATE INDEX IF NOT EXISTS idx_conversations_llm_profile ON conversations(llm_profile_id) WHERE llm_profile_id IS NOT NULL;`
- `CREATE INDEX IF NOT EXISTS idx_conversations_pinned ON conversations(pinned_at DESC) WHERE pinned_at IS NOT NULL;`

### 의미·소비·운영 경계

[`conversations` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-conversations)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-conversation-activities"></a>
## `conversation_activities`

`conversation_activities` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS conversation_activities (
    id                 BIGSERIAL PRIMARY KEY,
    conversation_id    BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
    worker             TEXT,
    kind               TEXT,
    tool               TEXT,
    tool_use_id        TEXT,
    is_error           BOOLEAN NOT NULL DEFAULT false,
    summary            TEXT,
    detail             TEXT,
    input_tokens       INTEGER,
    output_tokens      INTEGER,
    cache_read_tokens  INTEGER,
    cache_write_tokens INTEGER,
    created_at         TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 인덱스

- `CREATE INDEX IF NOT EXISTS idx_conv_act ON conversation_activities(conversation_id, id);`
- `CREATE INDEX IF NOT EXISTS idx_conv_act_tool_call ON conversation_activities(conversation_id, tool_use_id, id) WHERE kind IN ('tool_use', 'tool_result');`

### 의미·소비·운영 경계

[`conversation_activities` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-conversation-activities)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-agent-triggers"></a>
## `agent_triggers`

`agent_triggers` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS agent_triggers (
    id                          BIGSERIAL PRIMARY KEY,
    agent_key                   TEXT NOT NULL,
    enabled                     BOOLEAN NOT NULL DEFAULT true,
    interval_sec                INTEGER NOT NULL DEFAULT 0,
    on_finding                  BOOLEAN NOT NULL DEFAULT false,
    on_goal_met                 BOOLEAN NOT NULL DEFAULT false,
    on_task_timeout             BOOLEAN NOT NULL DEFAULT false,
    on_tool_call                BOOLEAN NOT NULL DEFAULT false,
    on_task_create              BOOLEAN NOT NULL DEFAULT false,
    interval_message            TEXT NOT NULL DEFAULT '',
    finding_message             TEXT NOT NULL DEFAULT '',
    goal_message                TEXT NOT NULL DEFAULT '',
    task_timeout_message        TEXT NOT NULL DEFAULT '',
    tool_call_message           TEXT NOT NULL DEFAULT '',
    task_create_message         TEXT NOT NULL DEFAULT '',
    tool_names                  TEXT NOT NULL DEFAULT '',
    last_fire                   TIMESTAMPTZ,
    created_at                  TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at                  TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 후속 변경·제약

- `ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS on_tool_call BOOLEAN NOT NULL DEFAULT false;`
- `ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS tool_call_message TEXT NOT NULL DEFAULT '';`
- `ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS tool_names TEXT NOT NULL DEFAULT '';`
- `ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS on_task_create BOOLEAN NOT NULL DEFAULT false;`
- `ALTER TABLE agent_triggers ADD COLUMN IF NOT EXISTS task_create_message TEXT NOT NULL DEFAULT '';`

### 인덱스

- `CREATE INDEX IF NOT EXISTS idx_agent_triggers_agent ON agent_triggers(agent_key);`

### 의미·소비·운영 경계

[`agent_triggers` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-agent-triggers)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-scheduler-state"></a>
## `scheduler_state`

`scheduler_state` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/schema.sql`다. [등록 근거](evidence:schema-core)

```sql
CREATE TABLE IF NOT EXISTS scheduler_state (
    key   TEXT PRIMARY KEY,
    value TEXT NOT NULL DEFAULT ''
);
```

### 의미·소비·운영 경계

[`scheduler_state` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-scheduler-state)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-llm-records"></a>
## `llm_records`

`llm_records` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/commands.go`다. [등록 근거](evidence:schema-db-commands)

```sql
CREATE TABLE IF NOT EXISTS llm_records (
    id            BIGSERIAL PRIMARY KEY,
    ts            TIMESTAMPTZ DEFAULT now(),
    model         TEXT,
    profile_name  TEXT,
    session_id    TEXT,
    task_id       TEXT,
    worker        TEXT,
    latency_ms    INTEGER,
    input_tokens  INTEGER,
    output_tokens INTEGER,
    cache_read    INTEGER,
    cache_write   INTEGER,
    status        TEXT,
    error         TEXT,
    request_body  TEXT,
    response_body TEXT,
    raw_request   TEXT,
    raw_response  TEXT
);
```

### 후속 변경·제약

Legacy table에는 `llmRecordsMigrate`가 `task_id`, `worker`, `profile_name`, `raw_request`, `raw_response`를 각각 `ADD COLUMN IF NOT EXISTS`로 더한다. `EnsureLLMRecordsTable`은 schema initializer와 같은 advisory-lock protocol에 참여하는 transaction에서 base DDL·index·이 ALTER를 실행한다. 어느 statement든 실패하면 해당 transaction은 commit되지 않지만 manager startup은 오류를 로그하고 계속하므로 table/column/index 가용성은 best-effort다.

### 인덱스

- `CREATE INDEX IF NOT EXISTS idx_llm_records_ts ON llm_records(ts);`
- `CREATE INDEX IF NOT EXISTS idx_llm_records_session ON llm_records(session_id);`

### 의미·소비·운영 경계

[`llm_records` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-llm-records)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.


<a id="table-llm-usage"></a>
## `llm_usage`

`llm_usage` 도메인의 영속 상태를 저장한다. 정확한 의미는 필드 정의와 사용 모듈을 함께 본다.

정의 원본은 `db/llm_usage.go`다. [등록 근거](evidence:schema-db-llm-usage)

```sql
CREATE TABLE IF NOT EXISTS llm_usage (
    id             BIGSERIAL PRIMARY KEY,
    ts             TIMESTAMPTZ NOT NULL DEFAULT now(),
    task_id        TEXT,
    exploration_id BIGINT,
    worker         TEXT,
    model          TEXT,
    profile_name   TEXT,
    latency_ms     INTEGER,
    input_tokens   INTEGER NOT NULL DEFAULT 0,
    output_tokens  INTEGER NOT NULL DEFAULT 0,
    cache_read     INTEGER NOT NULL DEFAULT 0,
    cache_write    INTEGER NOT NULL DEFAULT 0,
    status         TEXT
);
```

### 인덱스

- `CREATE INDEX IF NOT EXISTS idx_llm_usage_task ON llm_usage(task_id);`
- `CREATE INDEX IF NOT EXISTS idx_llm_usage_model ON llm_usage(task_id, model);`
- `CREATE INDEX IF NOT EXISTS idx_llm_usage_exp ON llm_usage(exploration_id);`

### 의미·소비·운영 경계

[`llm_usage` 필드 의미, reader/writer, index 비용과 retention 판정](semantic-catalog.md#semantic-llm-usage)에서 물리 DDL과 별도로 C/A/U를 확인한다. 운영 DB 적용·row 규모·query plan·role은 관찰하지 않았다.
