Skip to content

Release: rename to unify-agent + pipeline & ingest refactors + CVE fixes#262

Merged
djl11 merged 97 commits into
mainfrom
staging
Apr 20, 2026
Merged

Release: rename to unify-agent + pipeline & ingest refactors + CVE fixes#262
djl11 merged 97 commits into
mainfrom
staging

Conversation

@djl11

@djl11 djl11 commented Apr 20, 2026

Copy link
Copy Markdown
Member

Summary

Staging → main release (~100 commits since 2026-04-16).

Highlights from the team's work on staging:

  • Pipeline & ingest workers: shared unity/common/pipeline infrastructure, 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.
  • Native tabular parsers: polars-backed streaming CSV and openpyxl-backed XLSX backends (replacing Docling for these formats), with adaptive enrichment budgets and memory-bounded wave scheduling.
  • Comms refactor: assistant-owned shared primitives; outbound tools delegated to unity.comms.
  • M365 email: Unify-provisioned Outlook alongside Gmail; inbound email task triggers.
  • File manager: attachment dispatch routed through publish-parse-request; background ingestion pool; typed ingest retry policy.
  • Actor: consented-scope awareness; discovery-first on the minimal path.
  • Site: unity-agent GitHub Pages landing with Unity-voice hero copy.

Plus, from this review cycle:

  • PyPI distribution rename: unityunify-agent at v0.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.
  • Dependency CVE fixes: langchain-openai → 1.1.15, authlib → 1.7.0, langsmith → 0.7.33 (cascade: langchain-core → 1.3.0, new joserfc). Grep-verified no direct imports of these in unity → cascades can't break unity code paths.

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.

YushaArif99 and others added 30 commits April 16, 2026 10:32
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.
hmahmood24 and others added 26 commits April 20, 2026 01:21
…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."
@djl11 djl11 temporarily deployed to unity-testing April 20, 2026 23:29 — with GitHub Actions Inactive
@djl11 djl11 merged commit f34ff72 into main Apr 20, 2026
14 checks passed
djl11 added a commit that referenced this pull request Apr 21, 2026
…fy-llm"

Pivoting to a Hermes-style install script instead of publishing to PyPI.

Reverts the name rename and dep string changes back to unity/unify/unillm.
Lock regenerated; CVE fixes from 6fb4172 are preserved.

Original change: #262
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants