Conversation
Carry trigger attempt tokens through live execution and materialize live run rows only after work actually starts. This keeps Tasks/Runs truthful and prevents repeated trigger candidates from stealing provenance from the inbound that actually caused the run.
Capture task and source display strings in live run provenance so live task runs match the offline contract used by the Memory UI. Add focused coverage for the persisted display-field payload.
… assistant can access it for custom plan/actor
…pes and how to ref or use them before direct api calls
Clarify that discovery-first search narrows tool choice instead of defaulting to execute_code, and add prompt plus eval coverage for exact single-call requests.
Mark WhatsApp numbers and Discord ids as unique contact details so assistant-owned comms can resolve and attach them using the same strong-identifier rules as email and phone numbers.
Expose assistant-owned communication tools under `primitives.comms` and back those tools with one shared comms domain that can reserve durable outbound operations for headless task execution.
Route the live ConversationManager comms tools through the shared comms domain so live and programmatic assistant-owned sends follow the same contracts, docstrings, and transport patch points.
Explain the headless task-runner entrypoint in human terms so readers can see how Communication, `SingleFunctionActor`, and durable `Tasks/Runs` updates fit together without tracing the whole call chain.
Pin the slow-brain Discord DM flow so live inbound Discord messages are proven to route through the shared assistant-owned comms implementation and record the assistant reply on the Discord thread.
Verify that a plain-English outbound request leads CodeAct to infer the assistant-owned comms namespace on its own and settle on the expected Unify send primitive without extra comms calls.
Add job_title as a built-in Contact field (shorthand: jt) and seed it on the assistant contact (id=0) from the backend. Updates flow both ways: PATCH /admin/assistant on contact updates, and AssistantUpdateEvent refreshes the contact. broader_context now reads job_title from contact 0 and prepends Your main role / specialization is: <jobTitle>. to the prompt; the voice-call system prompt prepends the same to bio.
The native CSV backend uses polars' lazy scan_csv for zero-copy schema inference and streaming row extraction from arbitrarily large CSVs without materializing the entire file in memory. Pinned to >=1.39.3.
…ts, and OCR Extend FileParserSettings with three groups of knobs: - Adaptive enrichment: ENRICHMENT_MIN_TEXT_CHARS, PARAGRAPH_SUMMARY_MIN_CHARS, MAX_PARAGRAPH_SUMMARY_CALLS, MAX_SECTION_SUMMARY_CALLS to skip LLM calls on trivially short text and cap total calls per parse. - Tabular transport: TABULAR_SAMPLE_ROWS, TABULAR_INLINE_ROW_LIMIT, TABULAR_PROFILE_MAX_TABLES, TABULAR_PROFILE_MAX_SAMPLE_ROWS to control how many rows the native backends inline vs reference. - OCR: DOCLING_OCR_ENABLED defaults to True so scanned PDFs work out of the box; environments without OCR assets degrade gracefully via the fallback path added in a later commit.
Replace Docling-backed tabular parsing with purpose-built backends: - NativeCsvBackend: uses polars scan_csv for lazy schema inference and streaming row sampling. Rows below TABULAR_INLINE_ROW_LIMIT are inlined in ExtractedTable; larger files leave rows empty for deferred streaming via TableInputHandle. CSV dialect detection (delimiter, quotechar, encoding, header) uses csv.Sniffer plus a heuristic _looks_like_header fallback for files where Sniffer misidentifies the header row. - NativeExcelBackend: uses openpyxl in read_only mode with iter_rows() for constant-memory worksheet traversal. Same inline-vs-reference threshold logic via should_inline_tabular_rows. - Shared spreadsheet_support module: normalize_tabular_value (JSON-safe cell coercion), build_spreadsheet_graph (minimal ContentGraph for tabular files), finalize_spreadsheet_result (common result builder), should_inline_tabular_rows, take_sample_rows.
Point DEFAULT_BACKEND_CLASS_PATHS_BY_FORMAT for csv and xlsx to NativeCsvBackend and NativeExcelBackend respectively. The Docling-backed CsvBackend and MsExcelBackend remain importable for override configs but are no longer the default path.
The registry now routes csv to native_csv_backend and xlsx to native_excel_backend. Update three test files that asserted the old Docling-based names (csv_backend, ms_excel_backend).
Introduce the pipeline package with: - types.py: InlineRowsHandle, CsvFileHandle, XlsxSheetHandle, ObjectStoreArtifactHandle (sealed union via TableInputHandle), and ParsedFileBundle — a pipeline-owned wrapper pairing FileParseResult with per-table transport handles so ExtractedTable stays semantic. - row_streaming.py: iter_table_input_rows and iter_table_input_row_batches that resolve any TableInputHandle variant into a lazy row iterator, enabling chunked ingestion of arbitrarily large tables without full materialization. These types are the bridge between the parser boundary (which emits ExtractedTable with possibly-empty rows) and the ingest boundary (which needs to stream rows in batches).
The adapter now constructs a ParsedFileBundle for every parse result, creating the appropriate TableInputHandle per table: - InlineRowsHandle when ExtractedTable.rows is populated (small files) - CsvFileHandle when the source is CSV and rows were omitted (large files) - XlsxSheetHandle when the source is XLSX and rows were omitted This keeps the adapter as the single conversion point between parser output and FileManager ingestion input. Includes CSV dialect detection helpers (duplicated from csv_backend for adapter-layer independence).
Wire the typed transport layer into the FileManager ingest pipeline: - execute_ingest_table now accepts an optional TableInputHandle and iterates row batches via iter_table_input_row_batches instead of requiring all rows in memory. Falls back to InlineRowsHandle when called with legacy table_rows for backward compatibility. - process_single_file retrieves the matching TableInputHandle from adapted.bundle.table_inputs and passes it to execute_ingest_table. - build_compact_ingest_model uses num_rows (with fallback to len(rows)) and collects sheet_names from tables when content_rows don't provide them, fixing the metadata path for reference-first files.
… asset failure Two resilience improvements to the Docling conversion path: 1. PARTIAL_SUCCESS is now an accepted conversion status. When Docling returns it, the result is marked DEGRADED with warnings rather than treated as a hard failure. This handles PDFs where some pages converted successfully but others didn't. 2. When OCR is enabled (the default) and conversion fails because OCR model assets are unavailable (RapidOCR/PP-OCR download failure), docling_convert retries with a fresh converter that has do_ocr=False. The result is still returned as partial_success with a warning so downstream consumers know OCR was skipped. This prevents environments without network access to modelscope.cn from hard-failing on every PDF parse. The settings parameter is threaded through all Docling backends so the fallback logic has access to DOCLING_OCR_ENABLED. The new_docling_converter function now respects settings.DOCLING_OCR_ENABLED for pipeline_options.do_ocr.
…d RLIMIT_AS Three changes to the subprocess worker lifecycle: - subprocess_parse_single now resolves the file format and calls _pick_backend before apply_memory_limit. Heavy backend imports (polars, Docling ONNX models) complete before RLIMIT_AS restricts virtual address space, preventing import-time OOM in the child. - apply_memory_limit uses a 1 GB floor and respects existing hard limits so it works on systems with restrictive ulimits. - Forkserver preload and warm_imports now include polars alongside the Docling modules, keeping worker startup fast for CSV batches. - ParseConfig.max_subprocess_memory_pct defaults to 0.70 (was None) so subprocess isolation actually caps memory by default on Linux. New light_file_memory_pct (0.60) controls the wave scheduler budget. - memory_scheduler.estimate_peak_memory_bytes extracted as a public helper for the wave partitioner in file_parser.py.
…path Two batch scheduling improvements: 1. Single-file parse_batch calls now bypass the subprocess pool entirely, running in-process via _parse_single. This removes unnecessary worker lifecycle overhead for the common interactive single-file ingest path and eliminates subprocess-only failure modes for trivial batches. 2. Light files in subprocess mode are now grouped into memory-bounded waves via _partition_light_requests. Each wave's combined estimated peak memory stays under light_file_memory_pct of system RAM. This prevents submitting 20 light files concurrently when their aggregate memory would exceed available RAM. Also guards subprocess_isolation=False when _backends are injected or _parse_single is overridden, so test fixtures with monkey-patched backends don't accidentally try to pickle unpicklable mocks into child processes.
Skip expensive LLM summarization on trivially short text and cap total calls per document: - Paragraphs shorter than PARAGRAPH_SUMMARY_MIN_CHARS get their raw text as the summary (no LLM call). - After MAX_PARAGRAPH_SUMMARY_CALLS paragraph summaries, remaining paragraphs are clipped to the embedding budget without LLM calls. - Same pattern for sections via MAX_SECTION_SUMMARY_CALLS. - Root and metadata extraction skip the LLM when total text is below ENRICHMENT_MIN_TEXT_CHARS. This prevents runaway LLM costs on documents with hundreds of short paragraphs while preserving full enrichment quality on substantive text.
…ation Populate CsvDialect on ExtractedTable during native CSV parsing so build_table_handles can reuse it without re-sniffing. For large XLSX files exceeding the inline row limit, count remaining rows without materialising them, keeping num_rows accurate with bounded memory.
…pter cleanup Fix TypeError in build_parse_cost_line_items by extracting trace metrics instead of passing the whole parse_result object. Flip DiagnosticsConfig defaults (enable_progress, enable_run_ledger, enable_cost_ledger) to True so observability is on by default. Clean up adapter to use shared pipeline imports.
Add unit tests for TypeMap, prescan_from_rows, coerce_batch, and streaming dm.ingest via InlineRowsHandle. Add integration tests for CsvDialect propagation and XLSX count-only tail path. Fix existing test assertions: - test_file_pipeline_config_defaults: match new enable_progress=True - test_parse_supported: step name is 'llm_enrichment' not 'generate_hierarchical_summaries' - test_xlsx_multi_tab_per_table_context: filter by test prefix and assert exact per-sheet + per-file-storage-id contexts - test_artifact_store: update import path All data_manager and file_manager tests pass (parallel_run.sh -s). Pre-existing failures: test_insert_with_unique_keys (staging API 400), test_ops (empty file).
Centralise the parse-to-ingest handle construction in unity/common/pipeline/transport.py. Given a FileParseResult, builds the correct TableInputHandle per table (InlineRowsHandle for small in-memory tables, CsvFileHandle/XlsxSheetHandle for deferred streaming from source). Both the FM adapter and ingest_dm.py call this single implementation so transport logic is defined once.
… data
Production tabular dumps routinely contain string null sentinels ("NULL",
"NA", "N/A", "#N/A", empty), ragged rows with extra fields, and
schema-drift after the first N rows. Under the previous polars settings
any of these would fail the whole file with "could not parse 'NULL' as
dtype 'datetime'" or "found more fields than defined in Schema". Add a
shared _COMMON_NULL_SENTINELS list, bump infer_schema_length from 500 to
10_000, and enable truncate_ragged_lines + ignore_errors on every
scan_csv call site so a single malformed row never kills a multi-GB
file.
XLSX cell values from openpyxl and polars can be any Python object
(timedelta, bytes, Decimal, custom classes). Tighten
normalize_tabular_value into a guaranteed "JSON primitive or None"
coercion: pass through str/int/float/bool fast-path, isoformat datetime
types, stringify timedelta, utf-8-decode bytes, and str() anything else
as a safe fallback. This removes the "input was not a valid JSON value"
ExtractedTable validation errors without needing a new shim per exotic
type.
…n jobs Extend DeploymentJobState with a "cancelled" terminal state and give DeploymentIngestionJob two optional audit fields (cancelled_at, cancel_reason) so an operator-initiated abort is a first-class outcome rather than a synthetic error. On the queue side, add WorkQueue.is_cancelled(run_id) to the protocol and implement it on InMemoryWorkQueue with a companion cancel() method. Workers can now poll this between stages to short-circuit cleanly without relying on external run-ledger lookups. The Pub/Sub adapter already routes these calls through the deployment store, so no behavioural change is needed there.
run_ingestion_pipeline, ProcessFileFn, FileProcessingContext, FileWorkResult, and PipelineRunResult were introduced as a "shared orchestrator" for FM and DM ingestion. In practice nothing ever called them: FileManager.upload_and_parse drives its own dispatch via executor.py and ingest_dm.py calls ingest_artifacts directly. The lower-level shared components (ingest_artifacts, PipelineInstrumentation, transport.build_table_handles) already cover every real sharing need. Removing this layer cuts ~180 lines of speculative abstraction, removes a confusing second entry-point that would have drifted, and leaves the pipeline package with one coherent public surface per concern.
…kers
Conversational attachments previously parsed and ingested inside the
assistant pod via a ThreadPoolExecutor. This keeps heavy files pinned
to the CM process and does not share the retry, cancellation, and
observability surface of the deployment pipeline. Offload the work:
- Add AttachmentCallback to unity.common.pipeline.types and thread it
through ParseRequested / IngestRequested so the ingest worker knows
where to publish completion.
- Extend FileSettings with PIPELINE_DISPATCH_ENABLED and
PIPELINE_ARTIFACT_BUCKET (prefixed UNITY_FILE_, set on the assistant
pod in staging/production).
- In attachment_ingestion.enqueue, branch on the new flag: when enabled
we upload bytes to gs://{bucket}/attachments/{assistant_id}/... and
publish ParseRequested (thread="attachment_parse") to
unity-parse{env_suffix}; otherwise keep the existing in-process pool
for local dev. Add apply_attachment_completion() for the ack path.
- Handle the "attachment_ingestion_complete" thread in
CommsManager.dispatch_inbound_envelope and delegate to
apply_attachment_completion via ManagerRegistry.get_file_manager() on
a worker thread so FileRecords reflects the terminal status.
Naming follows the existing unity/comms Pub/Sub convention: production
has no suffix, other envs get "-{env}". Artifact bucket name is
derived the same way in helpers.py on the comms side.
Typed control-plane payloads for the pointer-only parse → ingest
handoff. ``IngestPlan`` is the manifest the parse worker publishes
after lowering: content rows and table bodies live behind handles so
the manifest stays KB-scale for any input size, and ``parse_summary``
is a stripped ``FileParseResult`` that keeps only what
``FileRecord.to_file_record_entry`` needs. ``TableMeta`` carries the
structural info the ingest worker needs to provision contexts without
rehydrating the full ``ExtractedTable`` list.
``FmBinding`` / ``DmBinding`` encode the two ingestion flavours: FM
activates a Unify context and lands rows under
``Files/{alias}/{storage_id}/...`` with a ``FileRecords`` entry; DM
writes raw rows to a target context. ``ParseRequested`` /
``IngestRequested`` gain ``ingestion_mode`` + bindings, and the
``file_paths`` docstring pins the one-file-per-message invariant
Tier-2 parallelism depends on.
``PipelineHeartbeatManifest`` is the lease-extender watchdog record:
``last_progress_at`` for ops to detect alive-but-stuck pods. Kept as
its own manifest type so it can live in a separate ``heartbeats.jsonl``
ledger without contending with stage/file/run writes.
…rialisation Centralise the "upload to GCS + publish ``ParseRequested``" dance so attachment ingestion, ``ingest_fm/dm --dispatch``, and the standalone operator CLI share one code path. The helper lives in ``unity/common/pipeline`` (next to ``ParseRequested``) to avoid a reverse dependency from OSS unity onto ``unity-deploy``. Invariants it enforces, with ``ValueError`` on violation: - one file per ``ParseRequested`` (load-bearing for Tier-2 parallelism) - ``ingestion_mode`` must match the supplied binding - exactly one of ``source_local_path`` / ``source_bytes`` / ``source_gs_uri``; ``gs://`` short-circuits the upload ``ArtifactStore.materialize_content_rows`` (+ ``LocalArtifactStore`` impl) is the sibling of ``materialize_table_input`` for the lowered ``FileContentRow`` stream the parse worker produces, pinned to a new ``CONTENT_ROWS_TABLE_ID`` constant so manifests stay consistent across stores. Tests cover all three invariants with in-memory fakes for ``storage.Client`` and ``pubsub_v1.PublisherClient``.
Two new methods on the ``WorkQueue`` protocol for the 24h graceful- shutdown path in unity-deploy: - ``extend_lease(receipt_id, seconds)``: called from a background ``LeaseExtender`` so long-running messages don't get redelivered to a second pod mid-work. Measured from *now* (not the existing deadline), matching Pub/Sub ``modify_ack_deadline`` semantics. - ``close()``: invoked during shutdown after the consumer loop stops, so Pub/Sub gRPC channels, publisher threads, and scheduled re-queue tasks exit cleanly before the process terminates. ``InMemoryWorkQueue`` implements ``extend_lease`` as a no-op and ``close`` as "cancel all pending requeue tasks and wait for them to settle". The new test registers a 60s retry then asserts ``close()`` completes well under a second — guarding the regression where a long retry delay would block shutdown for the full delay window.
…ess_plan worker entrypoint FM-mode half of the Option C parse → ingest handoff. ``parse_adapter.adapter.lower_to_ingest_plan`` is the canonical boundary the parse worker uses to hand work off without shipping heavy payloads through the queue manifest: it builds per-table ``TableInputHandle`` objects via the shared ``build_table_handles`` helper, runs ``lower_graph_to_content_rows`` and persists the stream via ``ArtifactStore.materialize_content_rows`` so no ``DocumentGraph`` crosses the wire, strips ``parse_result`` down to the fields ``FileRecord.to_file_record_entry`` still needs, and attaches ``TableMeta`` for context provisioning on the ingest side. The function is intentionally total — partial plans are still valid plans; per-table failures surface on the ingest-side run ledger. ``fm_process_plan`` is the worker-side sibling of ``fm_process_file``: given an ``IngestPlan`` it creates a ``FileRecord`` from the stripped summary, builds ``ArtifactWorkItem``s for content + per-table ingestion, and fans out through the same ``ingest_artifacts`` path the in-process executor uses. ``execute_ingest_content`` learns to accept either inline rows or a ``content_rows_handle`` (mutually exclusive), draining handles via ``iter_table_input_row_batches`` in ``content_rows_batch_size`` chunks so memory stays bounded on very large lowered documents.
…se_request
Replace the local ``_upload_bytes_to_gcs`` + ``_publish_to_topic``
plumbing in ``_dispatch_attachment_to_workers`` with a single call
into the shared ``publish_parse_request`` helper. Attachments now
share the one-file-per-``ParseRequested`` invariant and the same
upload / publish logic as operator dispatch scripts, so we only have
one place to harden going forward.
The attachment path sets ``ingestion_mode="fm"`` and builds an
``FmBinding`` with ``fm_alias="Local"`` — matching what the ingest
worker reconstructs via ``LocalFileSystemAdapter(root=None,
enable_sync=False)`` in ``_run_fm_mode`` — so conversational
attachments land under ``Files/Local/{storage_id}/...`` with a proper
``FileRecords`` entry rather than a bare DataManager context.
``user_id`` falls back to ``UNASSIGNED_USER_CONTEXT`` for the race
where dispatch fires before session binding; the upload prefix is
preserved so existing GCS lifecycle rules keep applying.
When the slow-brain turn raises a transient provider error (e.g. Anthropic HTTP 529 overloaded_error -> litellm.InternalServerError) after unillm's retry budget is exhausted, the only visible outcome was a "Slow-brain task failed" log line. The user's utterance was silently dropped and ProactiveSpeech continued emitting "still looking" filler for a request the slow brain had given up on. Wrap _run_llm in _run_llm_with_failure_notification. On transient LLM errors during voice modes (CALL/MEET) it now publishes a speakable FastBrainNotification (should_speak=True with explicit response_text so the fast brain utters the apology via TTS directly, bypassing its own LLM which may be hitting the same outage), cancels any pending proactive-speech cycle, and re-raises so the existing failure log is preserved. Non-transient errors (ValueError etc.) still bubble up unchanged so real defects stay visible. Regression coverage in tests/conversation_manager/voice/ test_slow_brain_failure_surfacing.py exercises the wrapper via a lightweight stub that binds the real methods: 529 Overloaded, 503 ServiceUnavailable, 429 RateLimit all surface; MEET mode takes the same path as CALL; success, TEXT mode, generic exceptions, and CancelledError do not trigger the apology; and the failure path never masks the underlying LLM exception even when the broker itself is down.
Shared FM and DM worker traffic now resolves the Unify api key per message via Orchestra's assistant endpoint, so the queue contract needs one explicit identity surface rather than duplicated subclass fields. Introduce IngestBinding as the frozen parent of FmBinding and DmBinding and require both user_id and assistant_id there to make the worker-side binding shape deterministic. Update the dispatch tests to lock in the inherited model shape and the new validation behaviour. This keeps the Unity side of the pipeline contract aligned with the assistant-scoped worker changes in unity-deploy.
When the slow brain answered a user message during cold-start initialization and InitializationComplete then fired, the pinned 'Review any earlier responses … and follow up if needed (correct, elaborate, or confirm)' notification was being read as permission to re-send a rephrased version of the pre-init reply. Observed in staging for assistant 1820 (Megan Richardson): one inbound message produced two distinct outbound replies ~10s apart. Rewrite the notification to enumerate the legitimate follow-up cases (deferred work, wrong/incomplete due to missing context, concrete update) and explicitly direct the brain to call wait otherwise, forbidding messages that simply rephrase, restate, or confirm a reply already given. The handler still always schedules the post-init brain turn so genuine deferred work (e.g. 'I'll look into it once I'm fully booted' → actually doing it) is preserved — suppressing the run entirely was considered but rejected as it silently drops those follow-ups. Lock the wording in via INITIALIZATION_COMPLETE_NOTIFICATION + a unit test that asserts the directive's key phrases. Add an eval (test_brain_completes_deferred_work_after_init) to guard the deferred-work path against future regressions.
Drop upload_prefix from DispatchTarget — _default_blob_key now always composes jobs/<job_id>/source/<basename>. Thread job_id through the ArtifactStore protocol so materialize_table_input/content_rows can scope writes under jobs/<job_id>/artifacts/. LocalArtifactStore ignores job_id (hash-based layout for dev ergonomics). ObjectStoreArtifactHandle gains source_local_path so the ingest worker can stage gs:// downloads before row streaming without making unity core GCS-aware.
Attachment dispatch now inherits the unified jobs/<job_id>/source/... layout via _default_blob_key like every other dispatch path. IMPLICIT_INGESTION remains False — this is a wiring-only change for when it is re-enabled.
Part of the unify-* PyPI family rename. The import name (unity) and the package layout are unchanged; only the distribution name and dep strings change. [tool.uv.sources] keys are renamed to match the new dep names, paths unchanged so the prod Dockerfile's sibling-clone install path (/unify, /unillm) continues to work.
Addresses dependabot alerts: - langchain-openai 1.1.12 -> 1.1.15 (image token counting SSRF via DNS rebinding, low) - authlib 1.6.9 -> 1.7.0 (CSRF when using cache, medium) - langsmith 0.7.30 -> 0.7.33 (streaming token events bypass redaction, medium) Cascade bumps: langchain-core 1.2.28 -> 1.3.0, added joserfc 1.6.4 (new authlib dep). Lock-only change; pyproject constraints already allowed these versions.
Adds a static landing page under site/ with a workflow that deploys it to GitHub Pages on pushes to main or staging. Dark emerald palette matching unify.ai, Hermes-style grid layout with uppercase monospace micro-labels, and a feature grid that mixes Hermes-parity capabilities (lives where you do, grows the longer it runs, runs in the background) with Unity's architectural differentiators (steerable nested execution, code-first planning, shared typed state).
Replaces the "Not X, not Y" hero lede and the "Lives where you do" / "Grows the longer it runs" tile titles with original Unity phrasing. Keeps the underlying capability claims (multichannel identity, background memory consolidation) but expressed in our own language: "One mind across every channel", "Consolidates as it runs", "Compounding structure, not compounding context". The hero now leads with "you host it" + "plans in real Python over typed state" instead of echoing Hermes's rhetorical structure.
Goes back to the punchier negation-then-assertion hero structure, but
replaces the Hermes-adjacent straw-men ("coding copilot in your IDE",
"chat wrapper around one API") with Unity's own: "single assistant
stuck in a chat window" and "tool menu in a trench coat" (the latter
already reused from the code-first feature tile). Positive claims now
lead with Unity-specific angles (real Python, live steering, shared
world) rather than Hermes's memory/duration framing. Also swaps the
bland "Sharper every turn" strip item for "Memory that compounds."
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Staging → main release (~100 commits since 2026-04-16).
Highlights from the team's work on staging:
unity/common/pipelineinfrastructure, typed work queue protocol, content-row materialization, heartbeat manifest, correlated observability + cost ledgers, publish-parse-request helper, background attachment ingestion pool, deployment bundle ingestion subpackage.unity.comms.unity-agentGitHub Pages landing with Unity-voice hero copy.Plus, from this review cycle:
unity→unify-agentatv0.1.0. Import name (unity) and package layout unchanged.[tool.uv.sources]editable paths preserved so the prod Dockerfile's sibling-clone install path continues to work.Risk
deploy/Dockerfile's editable sibling-clone path is preserved, so the prod base-image build is unaffected. CI's full test suite runs automatically on this PR.