agents/openai.yaml
interface: display_name: "Kanchi Dividend Us Tax Accounting" short_description: "Help with Kanchi Dividend Us Tax Accounting tasks"
tradermonty/claude-trading-skills · GitHub
Provide US dividend tax and account-location workflow for Kanchi-style income portfolios. Use when users ask about qualified vs ordinary dividends, 1099-DIV interpretation, REIT/BDC distribution treatment, holding-period checks, or taxable-vs-IRA account placement decisions for dividend assets.
프로젝트 폴더에서 아래 명령어를 실행하고, 설치할 에이전트를 선택하세요.
npx skills add tradermonty/claude-trading-skills --skill kanchi-dividend-us-tax-accounting설치 명령을 직접 실행해야 적용됩니다. 지원 에이전트와 필요한 권한·라이선스는 제작자의 안내를 확인하세요.
agents/openai.yamlinterface: display_name: "Kanchi Dividend Us Tax Accounting" short_description: "Help with Kanchi Dividend Us Tax Accounting tasks"
references/account-location-matrix.md# Account Location Matrix Use this matrix to propose placement between taxable and tax-advantaged accounts. ## Baseline Placement Logic | Instrument profile | Taxable account | Tax-advantaged account | Rationale | |---|---|---|---| | Qualified-dividend-heavy US equity | Usually preferred | Optional | Potentially better ongoing tax efficiency in taxable account | | REIT-heavy income holdings | Less preferred | Often preferred | Distribution may include higher ordinary-income components | | BDC/high-distribution structures | Less preferred | Often preferred | Tax treatment can be less favorable in taxable account | | MLP (partnership units) | Case-by-case | Caution in tax-advantaged accounts | UBTI and K-1 complexity can make placement non-trivial | | Broad index ETF with low turnover | Often acceptable | Also acceptable | Depends on overall asset-location design | If MLP is outside mandate, mark it explicitly as `OUT-OF-SCOPE` and exclude from default allocation logic. ## Conflict Resolution Rules When account-location recommendation conflicts with other needs: 1. Respect concentration and risk controls first. 2. Respect liquidity/withdrawal constraints second. 3. Optimize taxes third. ## Output Format Return one line per holding: ```text [Ticker] -> [Recommended Account] | Why: [one sentence] ```
references/annual-tax-memo-template.md# Annual Dividend Tax Planning Memo Template ```markdown # Annual Dividend Tax Planning Memo ([Year]) ## Filing Timeline - Tax year: - Filing deadline: - Extension status (filed/planned/not needed): - Expected filing date: ## Scope - Accounts included: - Holdings covered: - Data sources used: ## Assumptions - Rule set version/date: - Missing data assumptions: ## Distribution Classification Summary | Ticker | Account | Ordinary (est.) | Qualified (est.) | REIT/other components | Confidence | |---|---|---:|---:|---|---| | ... | ... | ... | ... | ... | High/Med/Low | ## Account-Location Actions | Ticker | Current location | Proposed location | Reason | |---|---|---|---| | ... | ... | ... | ... | ## Open Risks / Follow-Ups 1. ... 2. ... 3. ... ## Advisor Questions 1. ... 2. ... ``` Keep the memo concise and assumption-driven.
references/input-schema.md# Tax Planning Input Schema
Provide one JSON object with a non-empty `holdings` array.
```json
{
"holdings": [
{
"ticker": "JNJ",
"instrument_type": "stock",
"account_type": "taxable",
"security_type": "common",
"hold_days_in_window": 75
}
]
}
```
## Holding fields
- `ticker`: ticker symbol. Required operationally; missing values render as
`UNKNOWN` and must be corrected before acting.
- `instrument_type`: `stock`, `reit`, `bdc`, or `mlp`; defaults to `stock`.
- `account_type`: account label such as `taxable` or `ira`; defaults to
`unknown`.
- `security_type`: `common` or `preferred`; defaults to `common`.
- `hold_days_in_window`: non-negative integer holding days in the applicable
ex-dividend window. Missing or `null` values remain unknown: stock rows emit
`assumption_required`, while REIT/BDC/MLP rows retain their instrument-specific
planning labels. Strings, booleans, floats, and negative counts are rejected;
the CLI exits 1 with an error on stderr before writing reports.
For a not-yet-owned candidate, create a hypothetical row using the intended
account type and leave `hold_days_in_window` absent. Treat the resulting advice
as planning support, not a confirmed tax classification.
references/qualified-dividend-checklist.md# Qualified Dividend Checklist (US) Use this checklist for planning assumptions. ## Classification Pass For each holding, verify: 1. Distribution is potentially eligible for qualified treatment by instrument/type. 2. Shares meet required holding-period test around the ex-dividend date window. 3. No known disqualifying condition in the current fact pattern. If any item is uncertain, mark as `ASSUMPTION-REQUIRED`. ## Holding-Period Rules (US Federal, Common Planning Baseline) - Common stock baseline: hold shares for **more than 60 days** during the **121-day period** that starts **60 days before** the ex-dividend date. - Preferred stock (certain long-period dividends): often uses **more than 90 days** during a **181-day period** starting **90 days before** ex-dividend date. Use current IRS guidance as source of truth: - [IRS Publication 550, Qualified Dividends](https://www.irs.gov/publications/p550). - IRS Form 1099-DIV instructions. ## Script Planning Limitations `build_tax_planning_sheet.py` compares an operator-supplied day count against 61 days for common stock or 91 days for `security_type: preferred`. It does not derive the applicable window from acquisition, disposal, or ex-dividend dates, and it does not subtract days when the risk of loss was reduced. Prepare the qualifying day count before supplying it to the script. Publication 550 limits the preferred-stock 91-day test to dividends covering periods longer than 366 days; shorter-period preferred dividends follow the common-stock test. The script has no dividend-period field and applies the 91-day preferred threshold as a planning shortcut. Its result can therefore be more restrictive for shorter-period preferred dividends. Verify those cases manually against the applicable IRS window rather than treating the script label as a final tax determination. The same manual review applies to issuer eligibility and other conditions beyond the supplied day count. REIT and BDC rows retain an `ordinary_likely` planning label regardless of the day count. Actual distributions may contain multiple tax components; use broker/issuer breakdowns to determine the final treatment. ## Practical Data Fields Track these fields for each position: - Ticker - Account type - Ex-dividend date - Purchase date(s) - Disposal date(s), if any - Days held in required window - Preliminary classification (`qualified-likely`, `ordinary-likely`, `unknown`) ## Common Pitfalls - Assuming all common-stock dividends will be qualified without holding-period verification. - Ignoring short holding periods caused by frequent tactical trading. - Treating REIT/BDC distributions as identical to standard qualified-dividend flows. - **Modeling special / variable dividends as steady qualified income** (WS-8 hand-off from `kanchi-dividend-sop`). When the upstream `dividend_basis` carries `special_dividend_flag` or `variable_policy_flag` (e.g. ORI annual specials, CALM variable policy), treat that cash as **lumpy and non-recurring** for account-location and cash-flow planning: budget the *regular* run-rate as base income and the special/variable component separately (timing unpredictable; may still be qualified but must not be annualized as if recurring). ## Recommended Source Hierarchy 1. Broker tax documents and distribution breakdowns. 2. Official IRS publications/instructions for current-year rules (Publication 550, 1099-DIV instructions). 3. Issuer or fund notices when classification is revised.
requirements.txt# stdlib-only: packaged scripts use the Python standard library only.
scripts/build_tax_planning_sheet.py#!/usr/bin/env python3
"""Generate a deterministic US dividend tax planning sheet."""
from __future__ import annotations
import argparse
import csv
import json
import sys
from dataclasses import dataclass
from datetime import date
from pathlib import Path
from typing import Any
@dataclass
class PlanningRow:
ticker: str
instrument_type: str
account_type: str
hold_days_in_window: int | None
classification: str
location_hint: str
note: str
def required_days(security_type: str | None) -> int:
if str(security_type or "").strip().lower() == "preferred":
return 91
return 61
def classify_holding(holding: dict[str, Any]) -> PlanningRow:
ticker = str(holding.get("ticker", "")).strip().upper() or "UNKNOWN"
instrument_type = str(holding.get("instrument_type", "stock")).strip().lower()
account_type = str(holding.get("account_type", "unknown")).strip().lower()
security_type = str(holding.get("security_type", "common")).strip().lower()
hold_days_raw = holding.get("hold_days_in_window")
hold_days: int | None
if hold_days_raw is None:
hold_days = None
elif type(hold_days_raw) is not int or hold_days_raw < 0:
raise ValueError("hold_days_in_window must be a non-negative integer or null")
else:
hold_days = hold_days_raw
if instrument_type in {"reit", "bdc"}:
return PlanningRow(
ticker=ticker,
instrument_type=instrument_type,
account_type=account_type,
hold_days_in_window=hold_days,
classification="ordinary_likely",
location_hint="tax_advantaged_preferred",
note="distribution may include ordinary-income style components",
)
if instrument_type == "mlp":
return PlanningRow(
ticker=ticker,
instrument_type=instrument_type,
account_type=account_type,
hold_days_in_window=hold_days,
classification="out_of_scope_mlp",
location_hint="case_by_case",
note="check K-1 and UBTI implications before placement",
)
if hold_days is None:
return PlanningRow(
ticker=ticker,
instrument_type=instrument_type,
account_type=account_type,
hold_days_in_window=hold_days,
classification="assumption_required",
location_hint="taxable_preferred",
note="hold_days_in_window missing; cannot verify qualified treatment",
)
threshold = required_days(security_type)
if hold_days >= threshold:
classification = "qualified_likely"
note = f"hold_days_in_window >= {threshold}"
else:
classification = "ordinary_likely"
note = f"hold_days_in_window < {threshold}"
return PlanningRow(
ticker=ticker,
instrument_type=instrument_type,
account_type=account_type,
hold_days_in_window=hold_days,
classification=classification,
location_hint="taxable_preferred",
note=note,
)
def render_markdown(rows: list[PlanningRow], as_of: str) -> str:
lines = [
"# US Dividend Tax Planning Sheet",
"",
f"- as_of: `{as_of}`",
f"- holding_count: `{len(rows)}`",
"",
"| Ticker | Instrument | Account | Hold Days | Classification | Location Hint | Note |",
"|---|---|---|---:|---|---|---|",
]
for row in rows:
hold_days = "" if row.hold_days_in_window is None else str(row.hold_days_in_window)
lines.append(
f"| {row.ticker} | {row.instrument_type} | {row.account_type} | {hold_days} | "
f"{row.classification} | {row.location_hint} | {row.note} |"
)
lines.extend(
[
"",
"## Open Items",
"",
"- Confirm final classification against broker 1099-DIV and IRS current-year guidance.",
"- Escalate unresolved assumptions to CPA/tax advisor.",
"",
]
)
return "\n".join(lines)
def write_csv(path: Path, rows: list[PlanningRow]) -> None:
with path.open("w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(
[
"ticker",
"instrument_type",
"account_type",
"hold_days_in_window",
"classification",
"location_hint",
"note",
]
)
for row in rows:
writer.writerow(
[
row.ticker,
row.instrument_type,
row.account_type,
row.hold_days_in_window if row.hold_days_in_window is not None else "",
row.classification,
row.location_hint,
row.note,
]
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Build US dividend tax planning artifacts.")
parser.add_argument("--input", required=True, help="Path to JSON input with holdings list.")
parser.add_argument("--output-dir", default="reports", help="Output directory.")
parser.add_argument("--as-of", default=date.today().isoformat(), help="As-of date.")
return parser.parse_args()
def main() -> int:
args = parse_args()
payload = json.loads(Path(args.input).read_text())
holdings = payload.get("holdings", [])
if not isinstance(holdings, list) or not holdings:
raise SystemExit("Input JSON must include a non-empty holdings list.")
try:
rows = [classify_holding(item) for item in holdings]
except ValueError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 1
output_dir = Path(args.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
markdown_path = output_dir / f"tax_planning_sheet_{args.as_of}.md"
csv_path = output_dir / f"tax_planning_sheet_{args.as_of}.csv"
markdown_path.write_text(render_markdown(rows, args.as_of) + "\n", encoding="utf-8")
write_csv(csv_path, rows)
print(f"Wrote markdown: {markdown_path}")
print(f"Wrote csv: {csv_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
scripts/tests/conftest.py"""Shared fixtures for Kanchi US tax accounting script tests.""" import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
scripts/tests/test_build_tax_planning_sheet.py"""Tests for build_tax_planning_sheet.py."""
import csv
import json
import subprocess
import sys
from pathlib import Path
import build_tax_planning_sheet as tax
import pytest
from build_tax_planning_sheet import classify_holding, render_markdown, write_csv
def test_classify_stock_with_sufficient_days_is_qualified_likely() -> None:
row = classify_holding(
{
"ticker": "jnj",
"instrument_type": "stock",
"account_type": "taxable",
"hold_days_in_window": 75,
"security_type": "common",
}
)
assert row.classification == "qualified_likely"
def test_classify_missing_days_is_assumption_required() -> None:
row = classify_holding(
{
"ticker": "pg",
"instrument_type": "stock",
"account_type": "taxable",
}
)
assert row.classification == "assumption_required"
def test_classify_mlp_is_out_of_scope() -> None:
row = classify_holding(
{
"ticker": "et",
"instrument_type": "mlp",
"account_type": "ira",
"hold_days_in_window": 200,
}
)
assert row.classification == "out_of_scope_mlp"
assert row.location_hint == "case_by_case"
def test_markdown_and_csv_generation(tmp_path: Path) -> None:
rows = [
classify_holding(
{
"ticker": "o",
"instrument_type": "reit",
"account_type": "ira",
"hold_days_in_window": 100,
}
),
classify_holding(
{
"ticker": "jnj",
"instrument_type": "stock",
"account_type": "taxable",
"hold_days_in_window": 80,
}
),
]
markdown = render_markdown(rows, "2026-02-22")
assert "# US Dividend Tax Planning Sheet" in markdown
assert "| O | reit | ira | 100 | ordinary_likely |" in markdown
csv_path = tmp_path / "sheet.csv"
write_csv(csv_path, rows)
with csv_path.open() as f:
reader = list(csv.reader(f))
assert reader[0][0] == "ticker"
assert reader[1][0] == "O"
assert reader[2][0] == "JNJ"
@pytest.mark.parametrize(
"security_type,days,classification,threshold",
[
("common", 60, "ordinary_likely", 61),
("common", 61, "qualified_likely", 61),
("preferred", 90, "ordinary_likely", 91),
("preferred", 91, "qualified_likely", 91),
],
)
def test_holding_period_boundary(security_type, days, classification, threshold):
row = classify_holding(
{"ticker": "example", "security_type": security_type, "hold_days_in_window": days}
)
assert row.ticker == "EXAMPLE"
assert row.hold_days_in_window == days
assert row.classification == classification
assert row.location_hint == "taxable_preferred"
comparison = ">=" if classification == "qualified_likely" else "<"
assert row.note == f"hold_days_in_window {comparison} {threshold}"
@pytest.mark.parametrize("instrument", ["reit", "bdc"])
@pytest.mark.parametrize("days", [0, 200, None])
def test_reit_and_bdc_keep_ordinary_planning_branch(instrument, days):
row = classify_holding(
{"instrument_type": instrument, "security_type": "preferred", "hold_days_in_window": days}
)
assert row.instrument_type == instrument
assert row.hold_days_in_window == days
assert row.classification == "ordinary_likely"
assert row.location_hint == "tax_advantaged_preferred"
assert "may include" in row.note
@pytest.mark.parametrize("security_type", ["common", "preferred"])
def test_null_days_remain_an_unverified_assumption(security_type):
row = classify_holding({"security_type": security_type, "hold_days_in_window": None})
assert row.hold_days_in_window is None
assert row.classification == "assumption_required"
def test_zero_days_is_valid_but_insufficient():
row = classify_holding({"hold_days_in_window": 0})
assert row.hold_days_in_window == 0
assert row.classification == "ordinary_likely"
INVALID_DAYS = ["invalid", "61", 61.5, 61.0, True, False, -1, [], {}, float("nan"), float("inf")]
INVALID_IDS = [
"text",
"numeric-text",
"fractional",
"float",
"true",
"false",
"negative",
"array",
"object",
"nan",
"infinity",
]
@pytest.mark.parametrize("days", INVALID_DAYS, ids=INVALID_IDS)
def test_invalid_holding_days_have_a_descriptive_validation_error(days):
with pytest.raises(ValueError, match="hold_days_in_window must be a non-negative integer"):
classify_holding({"hold_days_in_window": days})
def _set_cli(monkeypatch, input_path, output):
monkeypatch.setattr(
sys,
"argv",
[
str(Path(tax.__file__)),
"--input",
str(input_path),
"--output-dir",
str(output),
"--as-of",
"2026-11-01",
],
)
@pytest.mark.parametrize("days", INVALID_DAYS, ids=INVALID_IDS)
def test_invalid_days_fail_cli_before_writing_reports(tmp_path, monkeypatch, capsys, days):
input_path = tmp_path / "input.json"
input_path.write_text(json.dumps({"holdings": [{"hold_days_in_window": days}]}))
output = tmp_path / "reports"
_set_cli(monkeypatch, input_path, output)
assert tax.main() == 1
captured = capsys.readouterr()
assert captured.out == ""
assert "ERROR:" in captured.err
assert "hold_days_in_window" in captured.err
assert "Traceback" not in captured.err
assert not output.exists()
def test_cli_writes_boundary_classifications_and_unknown_days(tmp_path, monkeypatch, capsys):
holdings = [
{"ticker": "C60", "security_type": "common", "hold_days_in_window": 60},
{"ticker": "C61", "security_type": "common", "hold_days_in_window": 61},
{"ticker": "P90", "security_type": "preferred", "hold_days_in_window": 90},
{"ticker": "P91", "security_type": "preferred", "hold_days_in_window": 91},
{"ticker": "UNKNOWN"},
{"ticker": "REIT", "instrument_type": "reit", "hold_days_in_window": 200},
{"ticker": "BDC", "instrument_type": "bdc", "hold_days_in_window": 200},
]
input_path = tmp_path / "input.json"
input_path.write_text(json.dumps({"holdings": holdings}))
output = tmp_path / "reports"
_set_cli(monkeypatch, input_path, output)
assert tax.main() == 0
captured = capsys.readouterr()
assert captured.err == ""
assert "Wrote markdown:" in captured.out
assert "Wrote csv:" in captured.out
with (output / "tax_planning_sheet_2026-11-01.csv").open(newline="") as handle:
rows = list(csv.DictReader(handle))
assert [row["classification"] for row in rows] == [
"ordinary_likely",
"qualified_likely",
"ordinary_likely",
"qualified_likely",
"assumption_required",
"ordinary_likely",
"ordinary_likely",
]
assert rows[4]["hold_days_in_window"] == ""
markdown = (output / "tax_planning_sheet_2026-11-01.md").read_text()
for ticker, days, classification in (
("C60", 60, "ordinary_likely"),
("C61", 61, "qualified_likely"),
("P90", 90, "ordinary_likely"),
("P91", 91, "qualified_likely"),
):
assert f"| {ticker} | stock | unknown | {days} | {classification} |" in markdown
assert "| UNKNOWN | stock | unknown | | assumption_required |" in markdown
assert "Confirm final classification" in markdown
def test_empty_holdings_stops_without_reports(tmp_path, monkeypatch):
input_path = tmp_path / "input.json"
input_path.write_text('{"holdings": []}')
output = tmp_path / "reports"
_set_cli(monkeypatch, input_path, output)
with pytest.raises(SystemExit, match="non-empty holdings list"):
tax.main()
assert not output.exists()
def test_standalone_cli_rejects_invalid_later_holding_without_overwriting(tmp_path):
input_path = tmp_path / "input.json"
input_path.write_text(
json.dumps(
{
"holdings": [
{"ticker": "VALID", "hold_days_in_window": 61},
{"ticker": "INVALID", "hold_days_in_window": "invalid"},
]
}
)
)
output = tmp_path / "reports"
output.mkdir()
existing = output / "tax_planning_sheet_2026-11-01.csv"
existing.write_text("existing report\n")
result = subprocess.run(
[
sys.executable,
str(Path(tax.__file__)),
"--input",
str(input_path),
"--output-dir",
str(output),
"--as-of",
"2026-11-01",
],
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 1
assert result.stdout == ""
assert "ERROR: hold_days_in_window" in result.stderr
assert "Traceback" not in result.stderr
assert existing.read_text() == "existing report\n"
assert list(output.iterdir()) == [existing]
SKILL.md--- name: kanchi-dividend-us-tax-accounting description: Provide US dividend tax and account-location workflow for Kanchi-style income portfolios. Use when users ask about qualified vs ordinary dividends, 1099-DIV interpretation, REIT/BDC distribution treatment, holding-period checks, or taxable-vs-IRA account placement decisions for dividend assets. --- # Kanchi Dividend Us Tax Accounting ## Overview Apply a practical US-tax workflow for dividend investors while keeping decisions auditable. Focus on account placement and classification, not legal/tax advice replacement. ## When to Use Use this skill when the user needs: - US dividend tax classification planning (qualified vs ordinary assumptions). - Holding-period checks before year-end tax planning. - Account-location decisions for stock/REIT/BDC/MLP income holdings. - A standardized annual dividend tax memo format. ## Prerequisites Prepare holding-level inputs: - `ticker` - `instrument_type` - `account_type` - `hold_days_in_window` (if available) Use the exact JSON contract and examples in `references/input-schema.md`. For deterministic output artifacts, provide JSON input and run: ```bash python3 skills/kanchi-dividend-us-tax-accounting/scripts/build_tax_planning_sheet.py \ --input /path/to/tax_input.json \ --output-dir reports/ ``` ## Guardrails Always state this clearly: tax outcomes depend on individual facts and jurisdiction. Treat this skill as planning support, then escalate final filing decisions to a tax professional. ## Workflow ### 1) Classify each distribution stream For each holding, classify expected cash flow into: - Potential qualified dividend. - Ordinary dividend/non-qualified distribution. - REIT/BDC-specific distribution components where applicable. Use `references/qualified-dividend-checklist.md` for holding-period and classification checks. ### 2) Validate holding-period eligibility assumptions For potential qualified treatment: - Check ex-dividend date windows. - Check required minimum holding days in the measurement window. - Flag positions at risk of failing holding-period requirement. If data is incomplete, mark status as `ASSUMPTION-REQUIRED`. ### 3) Map to reporting fields Map planning assumptions to expected tax-form buckets: - Ordinary dividend total. - Qualified dividend subset. - REIT-related components when reported separately. Use form terminology consistently so year-end reconciliation is straightforward. ### 4) Build account-location recommendation Use `references/account-location-matrix.md` to place assets by tax profile: - Taxable account for holdings likely to remain qualified-focused. - Tax-advantaged account for higher ordinary-income style distributions. When constraints conflict (liquidity, strategy, concentration), explain the tradeoff explicitly. ### 5) Produce annual planning memo Use `references/annual-tax-memo-template.md` and include: - Assumptions used. - Distribution classification summary. - Placement actions taken. - Open items for CPA/tax-advisor review. ## Output Always output: 1. Holding-level distribution classification table. 2. Account-location recommendation table with rationale. 3. Open-risk checklist for unresolved tax assumptions. 4. Optional generated artifacts from `skills/kanchi-dividend-us-tax-accounting/scripts/build_tax_planning_sheet.py`. ## Cadence Use this minimum rhythm: - Annually (60 min): full tax planning memo with account-location review. - Quarterly (15 min): refresh holding-period status for recent acquisitions. - Ad-hoc: rerun after material position changes, REIT/BDC additions, or triggered reviews from `kanchi-dividend-review-monitor`. ## Multi-Skill Handoff - Receive candidate and holding list from `kanchi-dividend-sop`. - Receive risk-event context (`WARN/REVIEW`) from `kanchi-dividend-review-monitor`. - Return account-location constraints back to `kanchi-dividend-sop` before new entries. ## Resources - `skills/kanchi-dividend-us-tax-accounting/scripts/build_tax_planning_sheet.py`: tax planning sheet generator. - `skills/kanchi-dividend-us-tax-accounting/scripts/tests/test_build_tax_planning_sheet.py`: tests for tax planning outputs. - `references/qualified-dividend-checklist.md`: classification and holding-period checks. - `references/account-location-matrix.md`: placement matrix by account type and instrument. - `references/annual-tax-memo-template.md`: reusable memo structure.