Metadata-Version: 2.5
Name: workerbee-sdk
Version: 1.0.0
Summary: Python SDK for the Workerbee platform: company workspaces, document ingestion and talent-graph chat.
Project-URL: Repository, https://github.com/Workerbee-Inc/workerbee-sdk
Project-URL: Documentation, https://docs.workerbee.ai/build/api-reference/python-sdk
Author: Workerbee
License: Apache-2.0
License-File: LICENSE
Classifier: Development Status :: 5 - Production/Stable
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.25
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# workerbee-sdk

Python SDK for the Workerbee platform. Use your company workspace automatically: upload resumes, job descriptions, performance reviews and other documents, then ask questions over your talent graph.

```bash
pip install ./workerbee_sdk-1.0.0-py3-none-any.whl   # built release artifact
```

> The PyPI name `workerbee` belongs to an unrelated project. This distribution is `workerbee-sdk` and imports as `workerbee_sdk`. Public registry publication is a separate follow-up.

## Quick start

See [runnable company-workspace samples](examples/README.md) for resume, JD,
review and ZIP uploads, file operations, and employee-specific skills queries.

```python
from workerbee_sdk import Workerbee, Message

# Reads WORKERBEE_API_KEY and uses production by default.
with Workerbee() as wb:
    file = wb.upload_file(
        "/path/to/mei-kowalski-resume.pdf",
        document_types=["resume"],
        external_id="emp-123",          # your stable employee ID
        source_system="hris",
        metadata={"team": "platform"},
    )
    file.wait_until_ready(timeout=300)   # raises a typed error unless PROCESSED

    resp = wb.chat(
        messages=[Message(role="user", content="List this employee's skills")],
        subject={"external_id": "emp-123", "source_system": "hris"},
    )
    print(resp.interpretation)
    for row in resp.rows:
        print(row)
```

Every chat response carries `interpretation`, ranked `rows`, `references` back to your own external IDs and source files, and an `audit` block. A field the platform did not return reads as `None`; it is never filled in client-side.

## Company workspace (automatic)

The API key identifies your company. On the first upload, file lookup/list or
chat request, the client resolves `GET /assistants/default`; the backend creates
or reuses that company's canonical default workspace. No company ID, workspace
ID or assistant creation step is needed. TIC's default workspace uses the same
record. Normal use needs file/chat scopes, not `assistants:manage`.

```python
with Workerbee() as wb:
    for file in wb.list_files(status="PROCESSED"):
        print(file.id, file.filename)
    file = wb.get_file("FILE_ID")
    file.download("./downloaded-source.pdf")
```

Keys restricted to other workspaces cannot access the default through these
methods; restrictions are not widened and no alternative workspace is silently
selected. Existing `wb.assistant` methods remain supported for integrations that
explicitly use other workspaces. Existing files are not moved or merged.
The service must support the `/assistants/default` route for this client
feature. Graph queries are company-scoped.

## API keys

Create a key in TIC (company settings → SDK API keys). The secret is shown once. Keys have scopes (`assistants:manage`, `files:write`, `files:read`, `graph:read`), can be restricted to specific assistants, can expire, and revocation takes effect on the next request. Dev and staging use separate keys. Production is the default: use `Workerbee()`. For development use `Workerbee(environment="dev")`; staging uses `environment="stage"`, and `environment="prod"` selects production explicitly. Use the key issued for that environment. An explicit `base_url` or `environment` overrides `WORKERBEE_BASE_URL`; otherwise that variable overrides the production default. Do not pass both `base_url` and `environment`.

## Documents

| Format | Notes |
| --- | --- |
| PDF, DOCX | Text extracted server-side |
| TXT, Markdown | UTF-8 (UTF-16 accepted) |
| JSON | Must be valid JSON; read as text in this release |
| ZIP | Each supported file becomes its own child file; see manifest below |
| Legacy .doc | Not yet supported — rejected with `FORMAT_NOT_YET_SUPPORTED` |

Limits (defaults): 25 MiB per document, 100 MiB per ZIP, 500 MiB expanded, 200 files per ZIP.

`document_types` declares what a document contains. Use one or more of `resume`, `job_description`, `performance_review`, `review`, or `other` on its own. Order and duplicates don't matter, `job_desc` is accepted as an alias, and a misspelling is rejected with a suggestion.

### Subjects

- `external_id="emp-123"` is shorthand when every declared type refers to one kind of entity: employee for `resume`/`performance_review`/`review`, job for `job_description`.
- A document that mixes kinds needs typed subjects:

```python
wb.upload_file(
    "offer-packet.pdf",
    document_types=["resume", "job_description"],
    subjects=[
        {"type": "employee", "external_id": "emp-123"},
        {"type": "job", "external_id": "req-456", "display_name": "Platform Engineer"},
    ],
)
```

`performance_review` and `review` documents must identify the employee.

### Revisions and idempotency

- `source_document_id` + `source_revision` name one immutable version. Re-sending the same revision with different bytes is a `ConflictError` (`SOURCE_REVISION_CONFLICT`).
- `idempotency_key` makes `upload_file` safe to retry. The same key with a different request is a `ConflictError`.

### ZIP manifest (optional)

Put `workerbee-manifest.json` (or `manifest.json`) at the archive root. A child's `document_types` replaces the archive default; it is not merged with it.

```json
{
  "files": {
    "resumes/mei.pdf": {"document_types": ["resume"], "external_id": "emp-123"},
    "reqs/platform.docx": {"document_types": ["job_description"], "external_id": "req-456"}
  }
}
```

An invalid child never discards its valid siblings. The archive ends as `PARTIAL_SUCCESS` and each child keeps its own status: `file.children_files()`.

## Processing status

`upload_file` returns once the bytes are stored and processing is queued. Server-side progress runs:

`PENDING_UPLOAD → QUEUED → VALIDATING → EXTRACTING → RESOLVING → PUBLISHING → PROCESSED`

`wait_until_ready()` returns only on `PROCESSED`. Any other settled state raises an exception, and `err.file` holds the full snapshot:

| Status | Exception | What to do |
| --- | --- | --- |
| `NEEDS_REVIEW` | `FileNeedsReviewError` | An identity match was uncertain; resolve it in TIC |
| `PARTIAL_SUCCESS` | `FilePartialSuccessError` | `file.retry()` re-runs only the failed types |
| `FAILED` | `FileFailedError` | See `err.code`; retry if `err.retryable` |
| `DELETED` | `FileDeletedError` | The file was deleted |
| (timeout) | `ReadyTimeoutError` | Processing continues on the server; poll again later |

Each declared type has its own result in `file.adapters`, and `file.has_queryable_content` says whether anything reached the graph.

### Publication and evidence

When evidence publication is available, the service publishes an immutable
snapshot for each source/type. `PROCESSED` waits for the graph to acknowledge
that snapshot. `file.readiness` is `graph_published` for published files;
older files may report `structuring_complete`.
`file.has_queryable_content` is false for empty documents and inactive replacements.

```python
with Workerbee() as wb:
    review = wb.upload_file(
        "review.pdf", document_types=["performance_review"],
        external_id="employee-123", source_system="hris",
        source_document_id="review-123-2026", source_revision="2",
        metadata={"review_date": "2026-09-24", "author": "Line manager"},
        idempotency_key="review-123-2026-v2",
    )
    review.wait_until_ready(timeout=600)
    for source in wb.evidence():
        for statement in source["snapshot"].get("statements", []):
            print(source["file_id"], statement["page"], statement["text"])
    answer = wb.chat(
        [Message(role="user", content="Show this employee's review evidence")],
        subject={"external_id": "employee-123", "source_system": "hris"},
    )
```

Review statements are attributed source quotes, not verified competencies. Dates and
authors supplied in metadata are reported attribution. Unknown employees enter
`NEEDS_REVIEW`; a company admin can link an existing person or explicitly create a
company-private person in TIC, record a reason, and resume processing.

Use the same `source_system` and `source_document_id` with a new `source_revision`
to replace a document. The previous version stays active until every replacement
adapter succeeds or finds no relevant content. A partial replacement remains inactive.
Deleting a source immediately hides every revision from SDK/TIC source-aware reads;
graph content is scrubbed asynchronously. Other active sources retain their evidence.
Publication retries reuse the original snapshot. Previously deferred review adapters
can be processed with `file.retry()` when review processing is available.

Legacy DOC and scanned PDF require server-side conversion support. Encrypted/corrupt files,
low-quality OCR, excessive page/pixel counts, and conversion timeouts return typed
errors. JSON remains text input.

Company scope remains the boundary; department/workspace isolation is not
provided by these APIs. Services without evidence support return 404 for
evidence APIs.

## Errors

All exceptions derive from `workerbee_sdk.errors.WorkerbeeError` and carry `code`, `status`, `request_id`, `retryable` and `details`. The SDK retries idempotent requests, 429s (honouring `Retry-After`) and connection errors up to `max_retries` times.

## Development

```bash
cd python
uv venv && uv pip install -e '.[dev]'
.venv/bin/python -m pytest
uv build          # dist/workerbee_sdk-<version>-py3-none-any.whl
```

### Employee-specific chat

Pass `subject={"external_id": "employee-123", "source_system": "hris"}` to `wb.chat(...)` to select an ingested employee without exposing platform IDs. Unknown or ambiguous identities return typed errors. `response.clarification` preserves supported follow-up questions from the graph.

For reproducible package validation, install the wheel with `requirements-validation.lock`. No public registry publication is implied.

### 0.1.0b5 sample fixes

`wb.chat(..., subject=...)` selects an employee directly. For example:

```python
response = wb.chat(
    messages=[Message(role="user", content="List this employee's skills")],
    subject={"external_id": "employee-123", "source_system": "hris"},
)
```

Employee skills queries require the matching `wb-graph` fix to be deployed.
They return the selected employee's projected skills (up to the response cap),
without company-wide strengths or gaps. Ingestion and graph projection are
asynchronous; an empty inventory does not fall back to other employees.
Omitting `subject` retains company-wide inventory behavior.

ZIP children inherit the archive uploader. After the backend fix is deployed,
retry a previously failed JD child with `wb.get_file(child_id).retry()`; legacy
children without ownership resolve it from a live archive in the same company
and workspace. No database migration is needed.

### 0.1.0b7 production default

`Workerbee()` now connects to production using `WORKERBEE_API_KEY`. Pass
`environment="dev"` or `environment="stage"` for non-production use. Custom
`base_url` and `WORKERBEE_BASE_URL` overrides remain supported. Existing b4/b5
wheels retain their original endpoint behavior.

## Call transcripts

Use `document_types=["call_transcript"]` to upload a written call or meeting transcript.
TXT, Markdown, JSON, PDF, DOCX, SRT, and VTT files are accepted, including entries in a ZIP.
SRT/VTT are read as text: speaker labels and timestamps remain in the source; no audio
transcription or speaker-to-employee inference is performed. Audio/video files are not accepted.

```python
with Workerbee() as wb:  # use environment="dev" for development
    transcript = wb.upload_file(
        "./customer-call.vtt",
        document_types=["call_transcript"],
        source_system="meetings",
        source_document_id="call-123",
        source_revision="1",
        metadata={"call_date": "2026-09-24", "title": "Project discussion"},
    )
    transcript.wait_until_ready(timeout=600)
    print(transcript.status, transcript.has_queryable_content)
    for adapter in transcript.adapters:
        print(adapter.document_type, adapter.status)
```

An employee `external_id` is optional; omit it for a company-level call. It identifies
an employee, not the call—use `source_document_id` for the call ID. An optional employee
reference does not create a new employee or infer the identities of other speakers.

The service stores the source and metadata, with authenticated download, deletion and revision
handling. The transcript adapter settles as `DEFERRED`, contributes no graph facts, and
a transcript-only file finishes as `PROCESSED` with `has_queryable_content=false`.
`PROCESSED` here means accepted/stored, not searchable or summarized. Transcript chat,
summaries, action items and skill extraction are not implemented. Existing Python clients
already accept the new type; deploy the matching backend and worker changes first.

## Mixed-document ZIP manifest

Place `workerbee-manifest.json` at the root of the ZIP. `manifest.json` is also
supported; prefer the Workerbee-specific name. Each key under `files` must exactly
match its file path inside the ZIP (use forward slashes). The manifest is control
information, not a document to ingest.

```json
{
  "files": {
    "resumes/employee.txt": {
      "document_types": [
        "resume"
      ],
      "external_id": "employee-123"
    },
    "jobs/engineer.md": {
      "document_types": [
        "job_description"
      ],
      "external_id": "job-456"
    },
    "reviews/employee.txt": {
      "document_types": [
        "performance_review"
      ],
      "external_id": "employee-123"
    },
    "calls/customer.vtt": {
      "document_types": [
        "call_transcript"
      ],
      "source_document_id": "call-789",
      "source_revision": "1"
    }
  }
}
```

Upload the ZIP using `wb.upload_file("./documents.zip", document_types=["other"],
source_system="hris")`. Each entry's `document_types` replaces the ZIP default.
Entries without a declaration inherit the ZIP default; filenames/extensions do not
auto-detect semantic document types. For an all-transcript ZIP, use
`document_types=["call_transcript"]` without a manifest.

The backend creates an independent child file, adapter and status for each entry.
Resume and JD children follow their own structuring pipelines; reviews require
an employee ID and follow the enabled review pipeline; call transcripts are
stored with a DEFERRED adapter and no searchable graph content. One bad type,
unsupported entry, or missing manifest path fails that child while valid siblings
continue. A malformed root manifest rejects the ZIP. Inspect
`archive.children_files()` for individual outcomes; mixed success is reported on
the parent. Source system defaults come from the ZIP upload; per-file employee/job
IDs, metadata, source_document_id and source_revision belong in each manifest entry.

## License

Licensed under the [Apache License, Version 2.0](LICENSE).
