Metadata-Version: 2.4
Name: hopfia-sdk
Version: 202608040951
Summary: Hopfia 에이전트 핸들러 SDK — 핸들러 계약과 런타임 하네스
Project-URL: Homepage, https://github.com/hopfia-ai/hopfia-sdk-python#readme
Project-URL: Repository, https://github.com/hopfia-ai/hopfia-sdk-python
Project-URL: Issues, https://github.com/hopfia-ai/hopfia-sdk-python/issues
License: MIT
License-File: LICENSE
Keywords: agent,hopfia,sdk
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# hopfia-sdk

**Hopfia 에이전트 핸들러 SDK — 파이썬판.** 핸들러 하나를 쓰고 `start_agent` 로 띄우면, 그 프로세스가 Hopfia 런타임이 부르는 컨테이너가 됩니다.

```bash
pip install hopfia-sdk
```

```python
import asyncio

from hopfia import AgentContext, AgentInput, start_agent


async def handler(input: AgentInput, ctx: AgentContext):
    yield ctx.text("티켓을 분류하겠습니다. ")

    grid = await ctx.artifacts.create(
        {"kind": "grid", "title": "티켓 분류 결과"},
        columns=[
            {"key": "ticket_id", "label": "티켓", "type": "string"},
            {"key": "category", "label": "분류", "type": "string"},
        ],
    )

    async for batch in classify(input):
        await grid.append_rows(batch)   # 흘리는 즉시 적재된다
    await grid.complete()

    yield ctx.text(f"총 {grid.row_count}건입니다.")


asyncio.run(start_agent(handler))  # ★ 이 줄이 있어야 컨테이너가 요청을 받습니다
```

**의존성이 없습니다.** 런타임 하네스가 HTTP 서버·클라이언트를 표준 라이브러리(`asyncio`·`urllib`)만으로 만드는 것도 같은 이유입니다 — 사용자 에이전트의 의존성 트리에 웹 프레임워크를 얹으면 그 버전 다툼이 곧 배포의 문제가 됩니다. `requires-python >= 3.11`.

## `start_agent` 가 감추는 것

**핸들러는 자기가 job 으로 도는지 pod 으로 도는지 모릅니다.** 실행 형태는 운영자가 환경마다 정하는 것이고, 그것이 사용자 코드로 새어 나가면 형태를 바꾸는 순간 배포가 깨집니다.

| | `job` 으로 기동 | `pod` 으로 기동 |
| --- | --- | --- |
| 입력 | 마운트된 페이로드 파일 / stdin 1회 | `POST /invoke` 반복 수신 |
| 동시 실행 | 1개. 끝나면 프로세스 종료 | N개. `HOPFIA_CONCURRENCY` 가 상한 |
| 파트 | 배치로 모아 올린다 | 응답 스트림으로 흘린다 |
| 취소 | 밖에서 태스크를 멈춘다(SIGTERM) | `POST /cancel { run_id }` — **그 요청 하나만** |
| `/healthz`·`/metrics` | 안 씀 | 하네스가 자동 제공 (사용자 코드 불필요) |

`ctx.log`·`ctx.progress`·`ctx.report_usage`·`ctx.artifacts`·`ctx.signal` 은 양쪽에서 **똑같이** 동작합니다.

## 인스턴스에서 무슨 일이 벌어지는지 보입니다

하네스가 컨테이너 stderr 로 진단 로그를 냅니다 — `ctx.log` 가 가는 Run 로그와 **다른 채널**이고, 핸들러가 남긴 것은 양쪽으로 갑니다.

```text
2026-08-03 19:06:12.604  info   pod 기동  version=202608031629 contract=1.0.0
2026-08-03 19:06:12.605  info   run_01J8AB  ▶ invoke  in_flight=1
2026-08-03 19:06:12.605  info   run_01J8AB  │ 티켓을 불러왔습니다  count=284
2026-08-03 19:06:12.706  warn   429 capacity  in_flight=1
2026-08-03 19:06:12.858  info   run_01J8AB  ↻ 재부착  backlog=8
2026-08-03 19:06:12.963  info   run_01J8AB  ◀ cancelled  took=0.36s parts=18
```

`HOPFIA_LOG_LEVEL`(기본 `info`) · `HOPFIA_LOG_FORMAT`(`text`|`json`) 으로 조절합니다 → [docs/102-harness.md](docs/102-harness.md#로그는-두-갈래입니다). 실행 형태를 고르는 값(`HOPFIA_EXEC_MODE`)도 Run 토큰도 `ctx` 에 실리지 않습니다(`INV-SDK-08`·`INV-SDK-07`).

## 문서

| | |
| --- | --- |
| [docs/100-handler.md](docs/100-handler.md) | 핸들러 계약 — 무엇을 받고 무엇을 `yield` 하는가, `ctx` 의 전부 |
| [docs/101-artifacts.md](docs/101-artifacts.md) | `ctx.artifacts` — grid·document·json, 그리고 지켜야 하는 것 |
| [docs/102-harness.md](docs/102-harness.md) | 하네스 — 두 실행 형태, 주입값, 배치·백프레셔·취소 |
| [docs/103-contracts.md](docs/103-contracts.md) | 계약은 어디서 오나 — 벤더링된 스키마와 생성기 |
| [docs/200-release.md](docs/200-release.md) | 버전 정책과 PyPI 배포 |
| [docs/300-migration.md](docs/300-migration.md) | 허브 저장소에서 이 저장소로 — 옮기는 순서 |

## 계약 버전

`hopfia-sdk` 의 버전과 **계약의 버전은 다른 축입니다.** 패키지 버전은 이 저장소가 올리고, 어느 계약을 구현했는지는 따로 답합니다.

```python
import hopfia

hopfia.__version__        # 패키지 버전
hopfia.CONTRACT_VERSION   # 이 빌드가 구현한 계약 버전
```

TypeScript 판 [`@hopfia/sdk`](https://www.npmjs.com/package/@hopfia/sdk) 와 **같은 계약**이고 이름만 파이썬 관례를 따릅니다. 두 패키지의 버전 번호는 더 이상 함께 움직이지 않습니다 — 같은 계약인지는 `CONTRACT_VERSION` 이 답합니다 → [docs/200-release.md](docs/200-release.md).

| | TypeScript | Python |
| --- | --- | --- |
| 이름 | `appendRows`·`rowCount` | `append_rows`·`row_count` |
| 취소 | `ctx.signal.throwIfAborted()` (`AbortSignal`) | `ctx.signal.raise_if_aborted()` → `RunCanceled` |
| `kind` 별 본체 필드 | `ArtifactSpec` 의 열린 키 | `create(spec, **body)` 의 키워드 인자 |

**JSON 키는 그대로 둡니다.** 계약의 키가 이미 `snake_case`(`artifact_id`·`tool_call_id`)라 파이썬 관례와 같습니다.

## 지금 무엇이 비어 있나

**하네스는 한 벌이 다 있지만, 그것이 말을 걸 허브 쪽이 아직 없습니다** — 제어 엔드포인트(`/v1/internal/runs/…`)와 `/invoke` 를 거는 게이트웨이, 그리고 Artifact API 입니다. 무엇이 언제 채워지는지는 허브 저장소의 `docs/600-roadmap.md` 「남겨 둔 자리」에 있습니다.

**그래도 하네스는 전부 검증돼 있습니다.** `ControlTransport` 가 함수 타입이라 대역을 꽂으면 되고, pod 은 포트 0 으로 띄워 실제 소켓에 말을 겁니다 — 게이트웨이 없이 105개가 돕니다.

## 개발

```bash
make install       # 개발 의존성 (패키지 자체는 의존성이 없다)
make check         # codegen-check → lint → typecheck → test. CI 가 도는 것과 같다
make codegen       # contracts/ → 생성물
make build         # check 뒤 sdist + wheel → dist/
```

**`src/hopfia/message_part.py` 와 `src/hopfia/artifact.py` 는 생성물입니다.** 손으로 고치면 다음 생성에서 되돌아갑니다 → [docs/103-contracts.md](docs/103-contracts.md).

## 라이선스

MIT. 서버는 비공개지만 SDK 는 사용자가 자기 에이전트에 설치하는 것이라, 설치할 때 쓸 권리를 따지게 만들면 그것 자체가 마찰입니다.
