Skip to content

fix(function_manager): check names and dependencies against the whole store in add_functions - #190

Open
breken-ai wants to merge 1 commit into
unifyai:mainfrom
breken-ai:fix/scoped-add-functions
Open

breken-ai wants to merge 1 commit into
unifyai:mainfrom
breken-ai:fix/scoped-add-functions

Conversation

@breken-ai

Copy link
Copy Markdown

Summary

FunctionManager.add_functions read existing names through list_functions(). That call applies the instance's filter_scope and exclude_compositional_ids, and the actor sets both at runtime. As a result, a stored function hidden from discovery was treated as if it did not exist during a write:

  • Dependency detection misses it. A new function that calls a hidden function is stored with depends_on == []. _inject_dependencies then never injects the dependency when the function is executed later, and delete_function(delete_dependents=True) no longer cascades to it.
  • overwrite=True inserts instead of updating. Adding a function with the same name as a hidden one runs an INSERT, which fails on UNIQUE constraint failed: functions.name. Because the inserts share one transaction, every other new function in that batch is rolled back with it.

The docstring of tests/function_manager/core/test_filter_scope.py states that "Write paths are unaffected" by filter_scope. list_function_name_to_ids() is documented as the unscoped catalogue "so a reference resolves even when its function is hidden from discovery". This PR makes add_functions use that catalogue for its name and dependency checks.

Type of change

  • Bug fix (non-breaking change that fixes incorrect behavior)
  • Feature (non-breaking change that adds functionality)
  • Refactor (no behavior change)
  • Breaking change (API or data-model change — Unify has zero-backward-compat policy, but please call it out)
  • Test-only (no source changes)
  • Docs / chore

Areas touched

  • Actor / CodeAct
  • ConversationManager / slow brain
  • A specific state manager (Contact / Knowledge / Transcript / Guidance / Function / File / Ingestion / Image / Web / Secret / Data / Memory)
  • Async tool loop (unify/common/_async_tool/)
  • Event bus / observability
  • The local store (unify/db/)
  • Tests / test infra (tests/, conftest.py, parallel_run.sh)
  • Build / packaging

Test plan

Two new regression tests in tests/function_manager/core/test_filter_scope.py:

  • test_scoped_add_records_dependency_on_hidden_function
  • test_scoped_overwrite_updates_hidden_function
# on main (fa912bec): both fail
AssertionError: depends_on [] != ['hello_world']
ValueError: Failed to add function(s): hello_world: error: Failed to create log - UNIQUE constraint failed: functions.name
2 failed, 9 passed

# on this branch
.venv/bin/python -m pytest tests/function_manager/ tests/guidance_manager/ tests/actor/code_act/test_directly_callable.py tests/actor/environments -m "not llm_call"

The broader run gives the same result on main and on this branch, except for the new tests. The only failures are 8 tests in test_live_steering_eval.py and test_default_env_steering.py, which need a live model key that I don't have. They fail the same way on main. black and autoflake are clean on the changed files.

  • All relevant tests pass locally
  • If this is a bug fix, I added a regression test (or explained why one isn't feasible)

Behavior / migration notes

None.

Checklist

  • Followed conventional commit style (feat(scope):, fix(scope):, refactor(scope):, chore(scope):, etc.)
  • No try/except added defensively — only around specific, recoverable errors
  • No "new" / "updated" / "TODO from chat" temporal comments (see .agents/rules/no-temporal-comments.md)
  • No test-specific shortcuts in production code (see .agents/rules/no-test-info-in-production-code.md)
  • Updated AGENTS.md / ARCHITECTURE.md if I changed architectural conventions (not applicable)

An AI agent (Breken, using Claude) found this bug, wrote the fix and the tests, and submitted the PR from the breken-ai account.

🤖 Generated with Claude Code

… store in add_functions

add_functions read existing names through list_functions, which applies the
instance's filter_scope and id exclusions. A function hidden from discovery
was then missing from dependency detection, so a new function calling it got
an empty depends_on (no runtime injection, no cascade on delete), and a
same-named add with overwrite=True tried an INSERT and failed on the UNIQUE
name constraint, rolling back the whole batch.

Use list_function_name_to_ids, the unscoped catalogue, for write-time checks.

This branch has not been deployed

No deployments
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.

1 participant