feat: implement task assignment subsystem with pluggable strategies#172
feat: implement task assignment subsystem with pluggable strategies#172
Conversation
Add three assignment strategies (Manual, RoleBased, LoadBalanced) behind a TaskAssignmentStrategy protocol, with a TaskAssignmentService orchestrator. Includes frozen Pydantic models, structured logging events, model validators, and comprehensive tests (unit + integration). Pre-reviewed by 10 agents, 25 findings addressed.
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (9)
📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughAdds a pluggable Task Assignment subsystem to the engine: domain models, a TaskAssignmentStrategy protocol, TaskAssignmentService orchestrator, three strategies (manual, role-based, load-balanced), config/schema wiring, new errors and observability events, and extensive unit/integration tests. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant Service as TaskAssignmentService
participant Strategy as TaskAssignmentStrategy
participant Scorer as AgentTaskScorer
Client->>Service: assign(AssignmentRequest)
activate Service
Service->>Service: validate task status
Service->>Service: log("task_assignment.started")
Service->>Strategy: assign(request)
activate Strategy
Strategy->>Scorer: score agents / build subtask
Scorer-->>Strategy: scored candidates
Strategy->>Strategy: filter by min_score
Strategy->>Strategy: select candidate (may use workloads)
Strategy-->>Service: AssignmentResult
deactivate Strategy
alt Agent selected
Service->>Service: log("task_assignment.agent.selected")
else No eligible agent
Service->>Service: log("task_assignment.no_eligible")
end
Service->>Service: log("task_assignment.complete")
Service-->>Client: AssignmentResult
deactivate Service
sequenceDiagram
participant Manual as ManualAssignmentStrategy
participant RoleBased as RoleBasedAssignmentStrategy
participant LoadBalanced as LoadBalancedAssignmentStrategy
participant Scorer as AgentTaskScorer
Note over Manual,Scorer: Strategy flows
rect rgba(100, 150, 255, 0.5)
Note over Manual: Manual Strategy
Manual->>Manual: validate assigned_to present & in pool & ACTIVE
Manual-->>Manual: return selected or raise
end
rect rgba(100, 200, 150, 0.5)
Note over RoleBased: Role-Based Strategy
RoleBased->>Scorer: build subtask & score agents
RoleBased->>RoleBased: filter by min_score, pick top score, populate alternatives
RoleBased-->>RoleBased: return selected or none
end
rect rgba(200, 150, 100, 0.5)
Note over LoadBalanced: Load-Balanced Strategy
LoadBalanced->>Scorer: score agents
LoadBalanced->>LoadBalanced: filter by min_score
LoadBalanced->>LoadBalanced: sort by workload asc, tie-break by score desc
LoadBalanced-->>LoadBalanced: return lowest-loaded selected
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a robust and flexible task assignment subsystem, significantly enhancing the platform's ability to intelligently distribute work among AI agents. By providing multiple pluggable strategies and a dedicated service for orchestration, it allows for dynamic and configurable task allocation based on factors like manual designation, agent capabilities, or current workload. This foundational change improves agent efficiency and system scalability, making the overall agent orchestration more sophisticated and adaptable to various operational needs. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a well-designed and robust task assignment subsystem. The use of a protocol for strategies allows for easy extension, and the initial three strategies (Manual, RoleBased, LoadBalanced) cover common use cases effectively. The code is clean, well-structured, and accompanied by comprehensive unit and integration tests. The new Pydantic models are well-defined and use validation effectively. I have one suggestion to further improve the robustness of the AssignmentRequest model by adding uniqueness validation for agent IDs in its collections. Overall, this is an excellent contribution.
| @model_validator(mode="after") | ||
| def _validate_non_empty_agents(self) -> Self: | ||
| """Ensure at least one agent is available for assignment.""" | ||
| if not self.available_agents: | ||
| msg = "available_agents must not be empty" | ||
| raise ValueError(msg) | ||
| return self |
There was a problem hiding this comment.
The AssignmentRequest model could be made more robust by validating the uniqueness of agent identifiers in available_agents and workloads. Currently, if there are duplicate agent IDs in these tuples, the logic in the assignment strategies might silently use the first occurrence or, in the case of workloads being converted to a dictionary, the last occurrence. This could lead to incorrect behavior or hide data integrity issues.
I suggest combining the existing _validate_non_empty_agents check with new uniqueness checks for both available_agents and workloads into a single, more comprehensive validator. This will make the system more predictable and easier to debug.
@model_validator(mode="after")
def _validate_request_collections(self) -> Self:
"""Ensure collections in the request are valid.
- `available_agents` must not be empty.
- Agent IDs in `available_agents` must be unique.
- Agent IDs in `workloads` must be unique.
"""
if not self.available_agents:
msg = "available_agents must not be empty"
raise ValueError(msg)
agent_ids = [a.id for a in self.available_agents]
if len(agent_ids) != len(set(agent_ids)):
from collections import Counter
dupes = sorted(str(i) for i, c in Counter(agent_ids).items() if c > 1)
msg = f"Duplicate agent IDs in available_agents: {dupes}"
raise ValueError(msg)
if self.workloads:
workload_agent_ids = [w.agent_id for w in self.workloads]
if len(workload_agent_ids) != len(set(workload_agent_ids)):
from collections import Counter
dupes = sorted(i for i, c in Counter(workload_agent_ids).items() if c > 1)
msg = f"Duplicate agent_id in workloads: {dupes}"
raise ValueError(msg)
return selfThere was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/ai_company/engine/assignment/models.py`:
- Around line 86-89: The workloads Field on the model can contain duplicate
AgentWorkload.agent_id entries which breaks assumptions in
LoadBalancedAssignmentStrategy; add a Pydantic validator (e.g.
`@validator`("workloads") or a root_validator) on the model that iterates the
workloads tuple, detects duplicate AgentWorkload.agent_id values, and raises a
ValueError (with a clear message) when duplicates are found so callers cannot
create models with non‑unique agent IDs.
In `@src/ai_company/engine/assignment/strategies.py`:
- Around line 140-144: The ID comparison relies on stringifying a UUID
(available.id) to match task.assigned_to; make this explicit and robust by
converting task.assigned_to to a UUID before comparison (or vice-versa) and
adding a short comment documenting the expectation. Update the loop that
iterates request.available_agents (the block that sets agent when
str(available.id) == task.assigned_to) to perform a safe conversion using
UUID(task.assigned_to) and catch/handle ValueError/TypeError if the string is
invalid, or normalize both sides to the same canonical representation, and add a
one-line comment documenting the chosen contract.
In `@tests/unit/engine/test_assignment_models.py`:
- Around line 309-318: Move the test_empty_available_agents_rejected test from
TestAssignmentRequestValidation into the existing TestAssignmentRequest class:
remove the TestAssignmentRequestValidation class wrapper and place the test
method (test_empty_available_agents_rejected) alongside the other tests in
TestAssignmentRequest so it runs with related AssignmentRequest tests; ensure
the test still imports/uses AssignmentRequest and _make_task() unchanged and
that the pytest.raises(ValidationError, match="available_agents") assertion
remains intact.
- Around line 288-296: Rename the misleading test method
test_selected_none_with_alternatives_is_valid to accurately reflect that it
asserts selected is None and alternatives is empty (e.g.,
test_selected_none_with_no_alternatives_is_valid or
test_selected_none_and_empty_alternatives_is_valid); update the test function
name where defined in tests/unit/engine/test_assignment_models.py and any
references to it so the name matches the docstring and the actual assertion on
AssignmentResult (constructed with task_id, strategy_used, reason).
- Around line 181-188: Update the test_min_score_out_of_range to cover both
below-zero and above-one cases by parametrizing the input values (e.g., -0.1 and
1.5) so that AssignmentRequest(task=_make_task(),
available_agents=(_make_agent("dev-1"),), min_score=...) raises ValidationError
for each; locate the test function test_min_score_out_of_range in
tests/unit/engine/test_assignment_models.py and modify it to iterate over the
invalid min_score values and assert ValidationError mentions "min_score".
In `@tests/unit/engine/test_assignment_service.py`:
- Around line 29-60: Extract the duplicated helpers _model_config, _make_agent,
and _make_task into a shared conftest.py as reusable fixtures (e.g.,
model_config, make_agent, make_task) that return the same types (ModelConfig,
AgentIdentity, Task) and keep the same default behavior; then update test
functions in the three test modules to accept and use those fixtures (or import
the helpers from conftest) instead of defining local copies, ensuring callers of
_make_agent still pass SeniorityLevel and primary_skills as before and that
_make_task supports overrides.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 149463df-ab12-4690-b485-5602fe4cfe1f
📒 Files selected for processing (20)
CLAUDE.mdDESIGN_SPEC.mdREADME.mdsrc/ai_company/config/defaults.pysrc/ai_company/config/schema.pysrc/ai_company/engine/__init__.pysrc/ai_company/engine/assignment/__init__.pysrc/ai_company/engine/assignment/models.pysrc/ai_company/engine/assignment/protocol.pysrc/ai_company/engine/assignment/service.pysrc/ai_company/engine/assignment/strategies.pysrc/ai_company/engine/errors.pysrc/ai_company/observability/events/task_assignment.pytests/integration/engine/test_multi_agent_delegation.pytests/unit/config/test_schema.pytests/unit/engine/test_assignment_models.pytests/unit/engine/test_assignment_service.pytests/unit/engine/test_assignment_strategies.pytests/unit/engine/test_errors.pytests/unit/observability/test_events.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Agent
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Do not use 'from future import annotations' in Python 3.14+ code
Use 'except A, B:' syntax without parentheses in exception handling for Python 3.14 (PEP 758)
Include type hints on all public functions; enforce with mypy strict mode
Use Google-style docstrings, required on all public classes and functions, enforced by ruff D rules
Create new objects rather than mutating existing ones; use copy.deepcopy() for non-Pydantic internal collections and MappingProxyType for read-only enforcement
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state
Use Pydantic v2 with BaseModel, model_validator, computed_field, and ConfigDict; use@computed_fieldfor derived values instead of storing redundant fields
Use NotBlankStr from core.types for all identifier/name fields instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code
Enforce maximum line length of 88 characters
Keep functions under 50 lines and files under 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, and config files
Files:
src/ai_company/engine/assignment/service.pysrc/ai_company/config/defaults.pysrc/ai_company/config/schema.pysrc/ai_company/engine/errors.pysrc/ai_company/engine/assignment/__init__.pytests/unit/engine/test_assignment_service.pytests/unit/engine/test_assignment_strategies.pysrc/ai_company/engine/assignment/models.pytests/unit/config/test_schema.pysrc/ai_company/observability/events/task_assignment.pytests/integration/engine/test_multi_agent_delegation.pysrc/ai_company/engine/assignment/protocol.pytests/unit/observability/test_events.pysrc/ai_company/engine/assignment/strategies.pytests/unit/engine/test_errors.pytests/unit/engine/test_assignment_models.pysrc/ai_company/engine/__init__.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Every module with business logic must import logger via 'from ai_company.observability import get_logger' and instantiate as 'logger = get_logger(name)'
Never use 'import logging' or 'logging.getLogger()' or 'print()' in application code
Always use 'logger' as the variable name for logging, not '_logger' or 'log'
Use event name constants from domain-specific modules under ai_company.observability.events for logging events
Always use structured logging with kwargs (logger.info(EVENT, key=value)), never formatted strings (logger.info('msg %s', val))
Log at WARNING or ERROR with context for all error paths before raising exceptions
Log at INFO for all state transitions
Log at DEBUG for object creation, internal flow, and entry/exit of key functions
Pure data models, enums, and re-exports do not require logging
All provider calls go through BaseCompletionProvider which applies retry and rate limiting automatically
Files:
src/ai_company/engine/assignment/service.pysrc/ai_company/config/defaults.pysrc/ai_company/config/schema.pysrc/ai_company/engine/errors.pysrc/ai_company/engine/assignment/__init__.pysrc/ai_company/engine/assignment/models.pysrc/ai_company/observability/events/task_assignment.pysrc/ai_company/engine/assignment/protocol.pysrc/ai_company/engine/assignment/strategies.pysrc/ai_company/engine/__init__.py
{src,tests}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names like 'example-provider', 'example-large-001', 'example-medium-001', 'example-small-001', 'large'/'medium'/'small'
Files:
src/ai_company/engine/assignment/service.pysrc/ai_company/config/defaults.pysrc/ai_company/config/schema.pysrc/ai_company/engine/errors.pysrc/ai_company/engine/assignment/__init__.pytests/unit/engine/test_assignment_service.pytests/unit/engine/test_assignment_strategies.pysrc/ai_company/engine/assignment/models.pytests/unit/config/test_schema.pysrc/ai_company/observability/events/task_assignment.pytests/integration/engine/test_multi_agent_delegation.pysrc/ai_company/engine/assignment/protocol.pytests/unit/observability/test_events.pysrc/ai_company/engine/assignment/strategies.pytests/unit/engine/test_errors.pytests/unit/engine/test_assignment_models.pysrc/ai_company/engine/__init__.py
DESIGN_SPEC.md
📄 CodeRabbit inference engine (CLAUDE.md)
Update DESIGN_SPEC.md to reflect approved deviations from the specification
Files:
DESIGN_SPEC.md
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Mark unit tests with@pytest.mark.unit
Mark integration tests with@pytest.mark.integration
Mark end-to-end tests with@pytest.mark.e2e
Mark slow tests with@pytest.mark.slow
Prefer@pytest.mark.parametrizefor testing similar cases
Use 'test-provider', 'test-small-001', etc. in test code instead of real vendor names
Files:
tests/unit/engine/test_assignment_service.pytests/unit/engine/test_assignment_strategies.pytests/unit/config/test_schema.pytests/integration/engine/test_multi_agent_delegation.pytests/unit/observability/test_events.pytests/unit/engine/test_errors.pytests/unit/engine/test_assignment_models.py
🧠 Learnings (3)
📚 Learning: 2026-03-08T09:48:46.483Z
Learnt from: CR
Repo: Aureliolo/ai-company PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T09:48:46.483Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state
Applied to files:
src/ai_company/engine/assignment/models.py
📚 Learning: 2026-03-08T09:48:46.483Z
Learnt from: CR
Repo: Aureliolo/ai-company PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T09:48:46.483Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under ai_company.observability.events for logging events
Applied to files:
src/ai_company/observability/events/task_assignment.pytests/unit/observability/test_events.py
📚 Learning: 2026-03-08T09:48:46.483Z
Learnt from: CR
Repo: Aureliolo/ai-company PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T09:48:46.483Z
Learning: Applies to src/ai_company/providers/**/*.py : RetryExhaustedError signals that all retries failed; engine layer catches this to trigger fallback chains
Applied to files:
src/ai_company/engine/__init__.py
🧬 Code graph analysis (9)
src/ai_company/engine/assignment/service.py (5)
src/ai_company/core/enums.py (1)
TaskStatus(165-191)src/ai_company/engine/errors.py (1)
TaskAssignmentError(66-67)src/ai_company/observability/_logger.py (1)
get_logger(8-28)src/ai_company/engine/assignment/models.py (2)
AssignmentRequest(64-111)AssignmentResult(114-154)src/ai_company/engine/assignment/protocol.py (3)
TaskAssignmentStrategy(16-41)assign(29-41)name(25-27)
tests/unit/engine/test_assignment_service.py (4)
src/ai_company/engine/assignment/models.py (1)
AssignmentRequest(64-111)src/ai_company/engine/assignment/service.py (2)
TaskAssignmentService(33-109)assign(46-109)src/ai_company/engine/assignment/strategies.py (7)
ManualAssignmentStrategy(100-194)name(108-110)name(210-212)name(275-277)assign(112-194)assign(214-257)assign(279-343)src/ai_company/engine/errors.py (1)
TaskAssignmentError(66-67)
tests/unit/engine/test_assignment_strategies.py (6)
src/ai_company/core/agent.py (3)
AgentIdentity(246-304)ModelConfig(145-174)SkillSet(125-142)src/ai_company/core/enums.py (4)
AgentStatus(67-72)Complexity(214-220)SeniorityLevel(6-21)TaskType(194-202)src/ai_company/engine/assignment/models.py (2)
AgentWorkload(16-36)AssignmentRequest(64-111)src/ai_company/engine/assignment/protocol.py (3)
TaskAssignmentStrategy(16-41)name(25-27)assign(29-41)src/ai_company/engine/assignment/strategies.py (9)
LoadBalancedAssignmentStrategy(260-343)ManualAssignmentStrategy(100-194)RoleBasedAssignmentStrategy(197-257)name(108-110)name(210-212)name(275-277)assign(112-194)assign(214-257)assign(279-343)src/ai_company/engine/routing/scorer.py (3)
AgentTaskScorer(36-162)score(74-162)min_score(70-72)
src/ai_company/engine/assignment/models.py (3)
src/ai_company/core/agent.py (1)
AgentIdentity(246-304)src/ai_company/core/task.py (1)
Task(45-261)src/ai_company/engine/routing/scorer.py (2)
score(74-162)min_score(70-72)
tests/unit/config/test_schema.py (1)
src/ai_company/config/schema.py (1)
TaskAssignmentConfig(361-393)
src/ai_company/engine/assignment/protocol.py (3)
src/ai_company/engine/assignment/models.py (2)
AssignmentRequest(64-111)AssignmentResult(114-154)src/ai_company/engine/assignment/strategies.py (6)
name(108-110)name(210-212)name(275-277)assign(112-194)assign(214-257)assign(279-343)src/ai_company/engine/assignment/service.py (1)
assign(46-109)
src/ai_company/engine/assignment/strategies.py (3)
src/ai_company/core/enums.py (1)
AgentStatus(67-72)src/ai_company/engine/errors.py (2)
NoEligibleAgentError(70-71)TaskAssignmentError(66-67)src/ai_company/engine/routing/scorer.py (2)
AgentTaskScorer(36-162)score(74-162)
tests/unit/engine/test_errors.py (1)
src/ai_company/engine/errors.py (3)
NoEligibleAgentError(70-71)TaskAssignmentError(66-67)EngineError(4-5)
tests/unit/engine/test_assignment_models.py (1)
src/ai_company/engine/assignment/models.py (4)
AgentWorkload(16-36)AssignmentCandidate(39-61)AssignmentRequest(64-111)AssignmentResult(114-154)
🪛 LanguageTool
README.md
[typographical] ~20-~20: To join two clauses or introduce examples, consider using an em dash.
Context: ...agent-task scoring - Task Assignment - Pluggable strategies (manual, role-based...
(DASH_RULE)
🔇 Additional comments (42)
src/ai_company/engine/errors.py (1)
64-71: LGTM!Clean addition to the engine error hierarchy. The two-level inheritance (
TaskAssignmentError→NoEligibleAgentError) provides appropriate granularity for distinguishing general assignment failures from the specific case of no eligible agents.src/ai_company/engine/assignment/protocol.py (1)
1-41: LGTM!Well-designed protocol with a clean, minimal interface. Good use of
TYPE_CHECKINGto avoid circular imports while maintaining type hints. The@runtime_checkabledecorator enables runtimeisinstance()checks for strategy validation.DESIGN_SPEC.md (1)
2389-2394: LGTM!Documentation accurately reflects the new assignment subsystem structure with proper file paths and descriptions that match the implementation.
src/ai_company/observability/events/task_assignment.py (1)
1-12: LGTM!Comprehensive set of event constants covering the full assignment lifecycle. Naming follows the established
domain.actionanddomain.entity.actionpatterns used by other event modules. Based on learnings, this correctly places event constants in a domain-specific module underai_company.observability.events.src/ai_company/engine/assignment/models.py (3)
16-36: LGTM!Good use of
allow_inf_nan=FalseinConfigDictto prevent invalid float values intotal_cost_usd. The field constraints (ge=0for count,ge=0.0for cost) are appropriate for workload tracking.
39-61: LGTM!Well-structured candidate model with appropriate score bounds and proper use of
NotBlankStrfor the reason field.
141-153: LGTM!Good defensive validation ensuring the selected candidate is not duplicated in alternatives. The error message includes the agent name for clear debugging.
src/ai_company/engine/assignment/strategies.py (4)
35-55: LGTM!Clean helper function that adapts
AssignmentRequesttoSubtaskDefinitionfor scoring. Good separation of concerns.
58-97: LGTM!Well-factored shared scoring logic with proper structured logging. The filtering and sorting by score descending provides a consistent candidate ranking for both RoleBased and LoadBalanced strategies.
312-321: Same ID type convention applies to workload lookup.The workload map keys are
agent_id(string fromAgentWorkload), and lookup usesstr(c.agent_identity.id). This is consistent with the manual strategy's approach but shares the same implicit contract about string UUID representation.
346-365: LGTM!Module-level singleton registry with
MappingProxyTypeenforces immutability. The_DEFAULT_SCORERensures consistent scoring across strategies. The type alias_StrategyTypeprovides clear typing for the registry values.src/ai_company/engine/assignment/service.py (4)
28-30: LGTM!Correct set of assignable statuses:
CREATEDfor new tasks,FAILEDandINTERRUPTEDfor retry scenarios. Usingfrozensetensures immutability.
33-44: LGTM!Clean service design with
__slots__for memory efficiency. The docstring correctly notes that the service does NOT mutate the task, maintaining single responsibility.
61-73: LGTM!Status validation with sorted assignable statuses in the error message provides a clear, deterministic error description. Proper use of WARNING level with context before raising.
82-90: LGTM!Good exception handling pattern: logs exception with
logger.exception(includes traceback) then re-raises. This ensures observability while preserving the original exception for callers.src/ai_company/engine/assignment/__init__.py (1)
1-39: LGTM!Clean package initialization that consolidates all public exports from the assignment subsystem. The
__all__list provides explicit API documentation and enables star imports when needed.src/ai_company/engine/__init__.py (1)
9-23: LGTM!The re-exports correctly expose the new task assignment subsystem's public API surface. All new symbols are properly added to
__all__in alphabetical order, and the imports are well-organized.Also applies to: 51-51, 55-55, 117-120, 129-132, 155-155, 157-157, 160-160, 177-177, 191-193
README.md (1)
20-20: LGTM!The new feature bullet accurately describes the task assignment subsystem and follows the existing formatting pattern used by other feature items in the list.
tests/integration/engine/test_multi_agent_delegation.py (4)
1-79: LGTM!Well-structured integration test module with proper markers (
integration,timeout(30)) applied globally viapytestmark. The mock provider implementation is clean and supports deterministic testing with canned responses and simulated failures.
189-210: LGTM!The
_make_agentfactory is well-designed with sensible defaults and uses test-compliant identifiers (test-provider,test-small-001). Thenoqa: PLR0913suppression is appropriate for a test factory function.
394-536: LGTM!The test scenarios provide comprehensive coverage:
- Happy path: delegation → decomposition → routing → parallel execution → rollup
- Partial failure: one subtask fails, rollup correctly derives FAILED status
- Loop prevention: back-delegation to ancestors is blocked
- Concurrency: max_concurrency limit is respected
- Assignment service: integration with RoleBasedAssignmentStrategy
848-873: LGTM!The
TestTaskAssignmentServiceIntegrationtest validates the full integration betweenTaskAssignmentService,RoleBasedAssignmentStrategy, andAgentTaskScorer. It correctly verifies that the backend agent is selected based on matching skills and role.src/ai_company/config/defaults.py (1)
32-32: LGTM!The empty dict default for
task_assignmentis correct. It aligns with other config sections and will be merged withTaskAssignmentConfig's field-level defaults during validation.CLAUDE.md (1)
52-52: LGTM!The engine description accurately reflects the addition of task assignment alongside routing, task lifecycle, recovery, and shutdown.
tests/unit/engine/test_errors.py (1)
11-13: LGTM!The tests correctly verify the error hierarchy:
TaskAssignmentErrorextendsEngineErrorNoEligibleAgentErrorextendsTaskAssignmentError(and transitivelyEngineError)String representation verification ensures error messages propagate correctly.
Also applies to: 48-61
tests/unit/config/test_schema.py (1)
13-13: LGTM!Comprehensive test coverage for
TaskAssignmentConfig:
- Default values verification
- Immutability (frozen model)
- Boundary validation for
min_score[0.0, 1.0] andmax_concurrent_tasks_per_agent[1, 50]- NaN/Inf rejection via
allow_inf_nan=FalseAlso applies to: 498-550
src/ai_company/config/schema.py (2)
361-393: LGTM!
TaskAssignmentConfigis well-designed and follows established patterns:
- Frozen for immutability
NotBlankStrfor strategy field (per coding guidelines)- Appropriate range constraints for
min_score[0.0, 1.0] andmax_concurrent_tasks_per_agent[1, 50]allow_inf_nan=Falseprevents invalid float values- Clear documentation including engine-layer usage note
417-418: LGTM!The
task_assignmentfield is correctly added toRootConfigwithdefault_factory=TaskAssignmentConfig, following the same pattern as other config sections likegraceful_shutdownandcoordination_metrics. The docstring is properly updated to document the new field.Also applies to: 482-485
tests/unit/engine/test_assignment_service.py (2)
1-26: LGTM!The module setup is well-structured with proper imports, the
pytestmark = pytest.mark.unitdecorator correctly marks all tests as unit tests, and the provider/model names comply with the coding guidelines ("test-provider", "test-small-001").
63-202: LGTM!The test class provides comprehensive coverage of
TaskAssignmentServicebehavior:
- Delegation to configured strategy
- Acceptance of assignable statuses (CREATED, FAILED, INTERRUPTED)
- Rejection of non-assignable statuses with proper error handling
- Error propagation from strategies
- Result contains strategy name
Good use of
@pytest.mark.parametrizefor testing similar cases as per coding guidelines.tests/unit/engine/test_assignment_models.py (3)
1-21: LGTM!Module setup is correct with proper imports and the
pytestmark = pytest.mark.unitmarker applied at module level.
58-103: LGTM!Thorough validation tests for
AgentWorkloadcovering:
- Valid construction
- Negative value rejection for both
active_task_countandtotal_cost_usd- Zero boundary case
- Immutability (frozen) enforcement
106-155: LGTM!Good boundary testing for
AssignmentCandidate.scorefield, correctly verifying that scores outside [0.0, 1.0] are rejected while boundary values are accepted.tests/unit/engine/test_assignment_strategies.py (6)
1-32: LGTM!Module setup is correct with proper imports and
pytestmark = pytest.mark.unitmarker. Thenoqa: PLR0913comment at line 39 appropriately suppresses the too-many-arguments warning for the_make_agenthelper.
73-147: LGTM!Comprehensive tests for
ManualAssignmentStrategycovering:
- Success with valid
assigned_to- Error when
assigned_toisNone- Error when designated agent is not in pool
- Rejection of inactive agents (parametrized for
ON_LEAVEandTERMINATED)- Strategy name property
Good use of
@pytest.mark.parametrizefor inactive agent status testing.
150-259: LGTM!Thorough tests for
RoleBasedAssignmentStrategycovering:
- Best-scoring agent selection
- Alternatives population
- No viable agents returns
Nonewhen below threshold- Seniority-only fallback when no required skills
- Strategy name property
262-480: LGTM!Excellent coverage for
LoadBalancedAssignmentStrategyincluding:
- Lowest workload selection
- Tie-breaking by score
- Fallback to capability-only when no workloads
- Parametrized workload distributions
- No eligible agents returns
None- Partial workload data defaults to zero
- Strategy name property
The parametrized test at lines 372-416 effectively covers edge cases with different workload distributions.
483-510: LGTM!Good registry tests verifying:
- All expected strategy keys are present
- Values are correct strategy types
- Map is immutable (MappingProxyType)
513-530: LGTM!Protocol conformance tests verify that all three strategy implementations (
ManualAssignmentStrategy,RoleBasedAssignmentStrategy,LoadBalancedAssignmentStrategy) satisfy theTaskAssignmentStrategyprotocol.tests/unit/observability/test_events.py (3)
87-96: LGTM!New task assignment event constants are properly imported. All eight events are included:
TASK_ASSIGNMENT_STARTEDTASK_ASSIGNMENT_COMPLETETASK_ASSIGNMENT_FAILEDTASK_ASSIGNMENT_NO_ELIGIBLETASK_ASSIGNMENT_AGENT_SCOREDTASK_ASSIGNMENT_AGENT_SELECTEDTASK_ASSIGNMENT_MANUAL_VALIDATEDTASK_ASSIGNMENT_WORKLOAD_BALANCEDBased on learnings: Uses event name constants from domain-specific modules under
ai_company.observability.eventsfor logging events.
165-165: LGTM!The
task_assignmentdomain module is correctly added to the expected set of domain modules for the discovery test.
266-274: LGTM!The test method correctly verifies all eight task assignment event constants map to their expected string values following the
domain.subject.qualifierpattern. The test is consistent with other*_events_existtest methods in the file.
| agent = None | ||
| for available in request.available_agents: | ||
| if str(available.id) == task.assigned_to: | ||
| agent = available | ||
| break |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Type conversion for ID comparison relies on consistent string representation.
The comparison str(available.id) == task.assigned_to works because available.id is a UUID and task.assigned_to is a string (presumably the UUID's string form). This implicit contract should be documented or enforced.
💡 Consider explicit documentation or a helper
+ # task.assigned_to stores agent ID as string; compare against UUID string form
for available in request.available_agents:
if str(available.id) == task.assigned_to:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/engine/assignment/strategies.py` around lines 140 - 144, The
ID comparison relies on stringifying a UUID (available.id) to match
task.assigned_to; make this explicit and robust by converting task.assigned_to
to a UUID before comparison (or vice-versa) and adding a short comment
documenting the expectation. Update the loop that iterates
request.available_agents (the block that sets agent when str(available.id) ==
task.assigned_to) to perform a safe conversion using UUID(task.assigned_to) and
catch/handle ValueError/TypeError if the string is invalid, or normalize both
sides to the same canonical representation, and add a one-line comment
documenting the chosen contract.
| def _model_config() -> ModelConfig: | ||
| return ModelConfig(provider="test-provider", model_id="test-small-001") | ||
|
|
||
|
|
||
| def _make_agent( | ||
| name: str, | ||
| *, | ||
| level: SeniorityLevel = SeniorityLevel.MID, | ||
| primary_skills: tuple[str, ...] = (), | ||
| ) -> AgentIdentity: | ||
| return AgentIdentity( | ||
| name=name, | ||
| role="Developer", | ||
| department="Engineering", | ||
| level=level, | ||
| model=_model_config(), | ||
| hiring_date=date(2026, 1, 1), | ||
| skills=SkillSet(primary=primary_skills), | ||
| ) | ||
|
|
||
|
|
||
| def _make_task(**overrides: object) -> Task: | ||
| defaults: dict[str, object] = { | ||
| "id": "task-001", | ||
| "title": "Test task", | ||
| "description": "A test task", | ||
| "type": TaskType.DEVELOPMENT, | ||
| "project": "proj-001", | ||
| "created_by": "manager", | ||
| } | ||
| defaults.update(overrides) | ||
| return Task(**defaults) # type: ignore[arg-type] |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider extracting shared test helpers to conftest.py.
The _model_config, _make_agent, and _make_task helpers are duplicated across test_assignment_service.py, test_assignment_models.py, and test_assignment_strategies.py. Extracting these to a shared conftest.py in the tests/unit/engine/ directory would reduce duplication and improve maintainability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/engine/test_assignment_service.py` around lines 29 - 60, Extract
the duplicated helpers _model_config, _make_agent, and _make_task into a shared
conftest.py as reusable fixtures (e.g., model_config, make_agent, make_task)
that return the same types (ModelConfig, AgentIdentity, Task) and keep the same
default behavior; then update test functions in the three test modules to accept
and use those fixtures (or import the helpers from conftest) instead of defining
local copies, ensuring callers of _make_agent still pass SeniorityLevel and
primary_skills as before and that _make_task supports overrides.
There was a problem hiding this comment.
Pull request overview
Implements a new engine-level task assignment subsystem with pluggable assignment strategies and supporting config, observability events, and test coverage to integrate assignment into the broader delegation/decomposition/routing pipeline.
Changes:
- Added task assignment domain models, strategy protocol + implementations, and an orchestration service.
- Added task-assignment config to the root configuration schema and defaults.
- Added structured observability event constants + updated docs and tests (unit + integration).
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/observability/test_events.py | Asserts discovery and exact values for new task-assignment event constants. |
| tests/unit/engine/test_errors.py | Verifies new assignment error types participate in the engine error hierarchy. |
| tests/unit/engine/test_assignment_strategies.py | Unit coverage for Manual/RoleBased/LoadBalanced strategies, registry, and protocol conformance. |
| tests/unit/engine/test_assignment_service.py | Unit coverage for TaskAssignmentService status validation, delegation, and error propagation. |
| tests/unit/engine/test_assignment_models.py | Unit coverage for assignment Pydantic models, validators, and immutability. |
| tests/unit/config/test_schema.py | Adds tests for TaskAssignmentConfig defaults, bounds, and NaN/Inf rejection. |
| tests/integration/engine/test_multi_agent_delegation.py | End-to-end integration exercising delegation→decomposition→routing→execution and assignment service integration. |
| src/ai_company/observability/events/task_assignment.py | Introduces the TASK_ASSIGNMENT_* event name constants. |
| src/ai_company/engine/errors.py | Adds TaskAssignmentError and NoEligibleAgentError. |
| src/ai_company/engine/assignment/strategies.py | Implements the three strategies and a module-level registry (STRATEGY_MAP). |
| src/ai_company/engine/assignment/service.py | Adds TaskAssignmentService orchestration, status validation, and logging. |
| src/ai_company/engine/assignment/protocol.py | Defines the TaskAssignmentStrategy protocol for pluggable strategies. |
| src/ai_company/engine/assignment/models.py | Adds frozen Pydantic request/result/candidate/workload models + validators. |
| src/ai_company/engine/assignment/init.py | Exposes assignment subsystem public API. |
| src/ai_company/engine/init.py | Re-exports assignment API at engine package level. |
| src/ai_company/config/schema.py | Adds TaskAssignmentConfig and wires it into RootConfig. |
| src/ai_company/config/defaults.py | Adds task_assignment default section in generated default config dict. |
| README.md | Documents task assignment as a supported feature. |
| DESIGN_SPEC.md | Updates project structure and observability event module list to include task assignment. |
| CLAUDE.md | Updates engine package description to mention task assignment. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| model_config = ConfigDict(frozen=True) | ||
|
|
||
| agent_identity: AgentIdentity = Field(description="Candidate agent") | ||
| score: float = Field( | ||
| ge=0.0, | ||
| le=1.0, | ||
| description="Match score (0.0-1.0)", | ||
| ) |
There was a problem hiding this comment.
AssignmentCandidate uses ConfigDict(frozen=True) without allow_inf_nan=False, so score could potentially accept NaN/Inf values even though it has range constraints. Consider setting allow_inf_nan=False (as done in other numeric-heavy models) to prevent non-finite scores from entering the assignment pipeline.
| model_config = ConfigDict(frozen=True) | ||
|
|
||
| task: Task = Field(description="The task to assign") | ||
| available_agents: tuple[AgentIdentity, ...] = Field( | ||
| description="Pool of agents to consider", | ||
| ) | ||
| workloads: tuple[AgentWorkload, ...] = Field( | ||
| default=(), | ||
| description="Current workload snapshots per agent", | ||
| ) | ||
| min_score: float = Field( | ||
| default=0.1, | ||
| ge=0.0, | ||
| le=1.0, | ||
| description="Minimum score threshold for eligibility", | ||
| ) |
There was a problem hiding this comment.
AssignmentRequest doesn't set allow_inf_nan=False, so min_score may accept NaN/Inf (Pydantic constraints don't reliably reject NaN unless configured). That can lead to confusing behavior (e.g., NaN comparisons filtering all candidates). Consider adding allow_inf_nan=False to the model config.
| model_config = ConfigDict(frozen=True) | ||
|
|
||
| task_id: NotBlankStr = Field(description="Task identifier") | ||
| strategy_used: NotBlankStr = Field( | ||
| description="Name of the strategy used", | ||
| ) | ||
| selected: AssignmentCandidate | None = Field( | ||
| default=None, | ||
| description="Selected candidate (None if no viable agent)", | ||
| ) | ||
| alternatives: tuple[AssignmentCandidate, ...] = Field( | ||
| default=(), | ||
| description="Other candidates considered, ranked by score", | ||
| ) | ||
| reason: NotBlankStr = Field(description="Explanation of decision") |
There was a problem hiding this comment.
AssignmentResult also lacks allow_inf_nan=False. If any strategy accidentally emits a non-finite score inside selected/alternatives, it will validate and then propagate into logs/metrics. Consider setting allow_inf_nan=False on this model as well for consistency with the rest of the codebase.
| try: | ||
| result = self._strategy.assign(request) | ||
| except Exception: | ||
| logger.exception( | ||
| TASK_ASSIGNMENT_FAILED, | ||
| task_id=task.id, | ||
| strategy=self._strategy.name, | ||
| ) | ||
| raise |
There was a problem hiding this comment.
The service logs logger.exception(...) for any strategy-raised exception and then re-raises. For expected/validated failures (e.g., TaskAssignmentError from ManualAssignmentStrategy), this will emit stack traces and can duplicate warnings already logged by the strategy. Consider catching TaskAssignmentError separately (log at warning/info without stack) and reserving logger.exception for unexpected exceptions only.
| class TaskAssignmentService: | ||
| """Orchestrates task assignment via a pluggable strategy. | ||
|
|
||
| Validates task status before delegating to the strategy. | ||
| Does NOT mutate the task — callers are responsible for any | ||
| subsequent status transitions. | ||
| """ | ||
|
|
||
| __slots__ = ("_strategy",) | ||
|
|
||
| def __init__(self, strategy: TaskAssignmentStrategy) -> None: | ||
| self._strategy = strategy | ||
|
|
||
| def assign(self, request: AssignmentRequest) -> AssignmentResult: |
There was a problem hiding this comment.
PR description says TaskAssignmentService “resolves strategy by name from STRATEGY_MAP”, but this implementation requires a concrete TaskAssignmentStrategy instance to be injected and never references STRATEGY_MAP. Either update the service to support name-based resolution (and likely accept a config/strategy name), or adjust the PR description/docs to match the actual API.
src/ai_company/config/schema.py
Outdated
| "Maximum concurrent tasks per agent. " | ||
| "Reserved for engine-layer enforcement — the engine " | ||
| "should pass this value to AssignmentRequest when " | ||
| "wiring up the assignment pipeline." | ||
| ), | ||
| ) | ||
|
|
||
|
|
There was a problem hiding this comment.
max_concurrent_tasks_per_agent's description says the engine “should pass this value to AssignmentRequest”, but AssignmentRequest currently has no field to carry this value and none of the strategies enforce it. Either remove/reword this guidance, or add the corresponding request field/enforcement wiring so the config is actionable.
| "Maximum concurrent tasks per agent. " | |
| "Reserved for engine-layer enforcement — the engine " | |
| "should pass this value to AssignmentRequest when " | |
| "wiring up the assignment pipeline." | |
| ), | |
| ) | |
| "Maximum concurrent tasks an agent is intended to handle. " | |
| "Actual enforcement must be implemented by the task " | |
| "assignment/engine layer." | |
| ), | |
| ) |
Greptile SummaryThis PR introduces a well-structured task assignment subsystem with three pluggable strategies ( Three issues warrant attention before merge:
Confidence Score: 3/5
Sequence DiagramsequenceDiagram
participant Caller
participant TaskAssignmentService
participant Strategy as TaskAssignmentStrategy<br/>(Manual / RoleBased / LoadBalanced)
participant AgentTaskScorer
participant STRATEGY_MAP
Caller->>TaskAssignmentService: assign(AssignmentRequest)
TaskAssignmentService->>TaskAssignmentService: validate task.status ∈ {CREATED, FAILED, INTERRUPTED}
alt invalid status
TaskAssignmentService-->>Caller: raise TaskAssignmentError
end
TaskAssignmentService->>Strategy: assign(request)
alt ManualAssignmentStrategy
Strategy->>Strategy: find agent by task.assigned_to
alt agent missing or inactive
Strategy-->>TaskAssignmentService: raise NoEligibleAgentError
end
Strategy-->>TaskAssignmentService: AssignmentResult(selected=candidate)
else RoleBased / LoadBalanced
Strategy->>AgentTaskScorer: score(agent, subtask) per ACTIVE agent
AgentTaskScorer-->>Strategy: RoutingCandidate (score 0.0–1.0)
Strategy->>Strategy: filter by min_score, sort by score / workload
alt no candidates above threshold
Strategy-->>TaskAssignmentService: AssignmentResult(selected=None)
else candidates found
Strategy-->>TaskAssignmentService: AssignmentResult(selected=best, alternatives=[...])
end
end
alt TaskAssignmentError raised
TaskAssignmentService-->>Caller: re-raise (no extra logging)
else unexpected exception
TaskAssignmentService->>TaskAssignmentService: logger.exception(TASK_ASSIGNMENT_FAILED)
TaskAssignmentService-->>Caller: re-raise
end
alt result.selected is not None
TaskAssignmentService->>TaskAssignmentService: log AGENT_SELECTED + COMPLETE
else result.selected is None
TaskAssignmentService->>TaskAssignmentService: log WARNING(NO_ELIGIBLE) + COMPLETE
end
TaskAssignmentService-->>Caller: AssignmentResult
Last reviewed commit: ddb34bb |
- Add inactive agent guard in scoring pipeline (CRITICAL) - Split broad except to avoid double-logging TaskAssignmentError - Add allow_inf_nan=False to all assignment models - Add uniqueness validators for agent IDs and workload entries - Update DESIGN_SPEC.md: §6.4 current state, §15.3 engine dir, §15.5 conventions - Extract _find_designated_agent for ManualAssignmentStrategy - Add __slots__ to ManualAssignmentStrategy - Clarify max_concurrent_tasks_per_agent description - Add WARNING log when no eligible agent found - Strengthen protocol docstring for sync-only requirement - Parametrize min_score boundary tests, add blank/NaN/Inf edge cases - Add unexpected exception propagation test - Add LoadBalanced integration test with workloads - Merge TestAssignmentRequestValidation into TestAssignmentRequest
| workload_map: dict[str, int] = { | ||
| w.agent_id: w.active_task_count for w in request.workloads | ||
| } | ||
|
|
||
| if workload_map: | ||
| candidates.sort( | ||
| key=lambda c: ( | ||
| workload_map.get(str(c.agent_identity.id), 0), | ||
| -c.score, | ||
| ), | ||
| ) | ||
| logger.debug( | ||
| TASK_ASSIGNMENT_WORKLOAD_BALANCED, | ||
| task_id=request.task.id, | ||
| agent_name=candidates[0].agent_identity.name, | ||
| workload=workload_map.get( | ||
| str(candidates[0].agent_identity.id), | ||
| 0, | ||
| ), | ||
| ) | ||
|
|
There was a problem hiding this comment.
max_concurrent_tasks_per_agent limit is declared but never enforced
TaskAssignmentConfig.max_concurrent_tasks_per_agent (lines 383–391) is configured as the "maximum concurrent tasks an agent is intended to handle", but LoadBalancedAssignmentStrategy never reads or respects this limit. It sorts by ascending workload and picks the minimum, but will happily select an agent that already has 10 active tasks if that is the lowest workload in the pool—even when the config says the limit is 5.
The config field's own docstring acknowledges this gap ("Actual enforcement must be implemented by the engine/assignment layer"), but no such enforcement exists in either the service or the strategies. This means the config value is decorative and the feature is half-implemented.
To properly enforce the cap, add max_concurrent_tasks_per_agent: int to AssignmentRequest so the limit flows from config into the request, then filter candidates in the workload-sorting block before selecting the winner:
if workload_map:
cap = request.max_concurrent_tasks_per_agent
candidates = [
c for c in candidates
if workload_map.get(str(c.agent_identity.id), 0) < cap
]Without this connection, the config field provides no runtime safety guarantee.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ai_company/engine/assignment/strategies.py
Line: 343-363
Comment:
`max_concurrent_tasks_per_agent` limit is declared but never enforced
`TaskAssignmentConfig.max_concurrent_tasks_per_agent` (lines 383–391) is configured as the "maximum concurrent tasks an agent is intended to handle", but `LoadBalancedAssignmentStrategy` never reads or respects this limit. It sorts by ascending workload and picks the minimum, but will happily select an agent that already has 10 active tasks if that is the lowest workload in the pool—even when the config says the limit is 5.
The config field's own docstring acknowledges this gap ("Actual enforcement must be implemented by the engine/assignment layer"), but no such enforcement exists in either the service or the strategies. This means the config value is decorative and the feature is half-implemented.
To properly enforce the cap, add `max_concurrent_tasks_per_agent: int` to `AssignmentRequest` so the limit flows from config into the request, then filter candidates in the workload-sorting block before selecting the winner:
```python
if workload_map:
cap = request.max_concurrent_tasks_per_agent
candidates = [
c for c in candidates
if workload_map.get(str(c.agent_identity.id), 0) < cap
]
```
Without this connection, the config field provides no runtime safety guarantee.
How can I resolve this? If you propose a fix, please make it concise.| if not candidates: | ||
| logger.warning( | ||
| TASK_ASSIGNMENT_NO_ELIGIBLE, | ||
| task_id=request.task.id, | ||
| strategy=self.name, | ||
| agent_count=len(request.available_agents), | ||
| min_score=request.min_score, | ||
| ) | ||
| return AssignmentResult( | ||
| task_id=request.task.id, | ||
| strategy_used=self.name, | ||
| reason=( | ||
| f"No agents scored above threshold " | ||
| f"{request.min_score} for task {request.task.id!r}" | ||
| ), | ||
| ) |
There was a problem hiding this comment.
Inconsistent "no eligible agent" signaling across strategies
ManualAssignmentStrategy raises NoEligibleAgentError when the designated agent cannot be found or is inactive (lines 170, 186). RoleBasedAssignmentStrategy (here) and LoadBalancedAssignmentStrategy return AssignmentResult(selected=None, ...) instead.
While TaskAssignmentService handles both patterns correctly, the inconsistency leaks to downstream callers: a caller that only guards against TaskAssignmentError exceptions will silently receive a result with selected=None when a scoring strategy finds no eligible agents, and may proceed without an actual assignment.
The TaskAssignmentStrategy protocol's docstring should either:
- Document the contract explicitly—that either
NoEligibleAgentErroris raised ORselected=Noneis returned, and when each is used, or - Standardize the behavior across all three strategies to always raise
NoEligibleAgentErrorwhen no eligible agent exists.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ai_company/engine/assignment/strategies.py
Line: 261-276
Comment:
Inconsistent "no eligible agent" signaling across strategies
`ManualAssignmentStrategy` raises `NoEligibleAgentError` when the designated agent cannot be found or is inactive (lines 170, 186). `RoleBasedAssignmentStrategy` (here) and `LoadBalancedAssignmentStrategy` return `AssignmentResult(selected=None, ...)` instead.
While `TaskAssignmentService` handles both patterns correctly, the inconsistency leaks to downstream callers: a caller that only guards against `TaskAssignmentError` exceptions will silently receive a result with `selected=None` when a scoring strategy finds no eligible agents, and may proceed without an actual assignment.
The `TaskAssignmentStrategy` protocol's docstring should either:
1. Document the contract explicitly—that either `NoEligibleAgentError` is raised OR `selected=None` is returned, and when each is used, or
2. Standardize the behavior across all three strategies to always raise `NoEligibleAgentError` when no eligible agent exists.
How can I resolve this? If you propose a fix, please make it concise.| strategy: NotBlankStr = Field( | ||
| default="role_based", | ||
| description="Assignment strategy name", | ||
| ) |
There was a problem hiding this comment.
strategy field is not validated against known strategy names
TaskAssignmentConfig.strategy accepts any NotBlankStr with no validator checking it against the actual entries in STRATEGY_MAP ("manual", "role_based", "load_balanced"). A misconfigured value like "auction" or a typo like "role_base" passes config validation cleanly and will only fail (or be silently ignored) when the caller tries to resolve the strategy at runtime.
Add a @model_validator method to assert the value is a known key. The allowed set can be defined as a constant in engine/assignment/strategies.py to avoid circular imports between config and engine modules.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ai_company/config/schema.py
Line: 373-376
Comment:
`strategy` field is not validated against known strategy names
`TaskAssignmentConfig.strategy` accepts any `NotBlankStr` with no validator checking it against the actual entries in `STRATEGY_MAP` (`"manual"`, `"role_based"`, `"load_balanced"`). A misconfigured value like `"auction"` or a typo like `"role_base"` passes config validation cleanly and will only fail (or be silently ignored) when the caller tries to resolve the strategy at runtime.
Add a `@model_validator` method to assert the value is a known key. The allowed set can be defined as a constant in `engine/assignment/strategies.py` to avoid circular imports between `config` and `engine` modules.
How can I resolve this? If you propose a fix, please make it concise.PR #174 — git_worktree.py: - _validate_git_ref now accepts error_cls/event params so merge context raises WorkspaceMergeError and teardown raises WorkspaceCleanupError - _run_git catches asyncio.CancelledError to kill subprocess before re-raising, preventing orphaned git processes PR #172 — task assignment: - TaskAssignmentConfig.strategy validated against known strategy names - max_concurrent_tasks_per_agent now enforced in _score_and_filter_candidates via new AssignmentRequest.max_concurrent_tasks field - TaskAssignmentStrategy protocol docstring documents error signaling contract PR #171 — worktree skill: - rebase uses --left-right --count with triple-dot to detect behind-main - setup reuse path uses correct git worktree add (without -b) - setup handles dirty working tree with stash/abort prompt - status table shows both ahead and behind counts - tree command provides circular dependency recovery guidance PR #170 — meeting parsing: - Fix assigned? regex to assigned (prevents false-positive assignee extraction from "assign to X" in action item descriptions)
…176) ## Summary - Fix CI failures on main: 2 test assertion mismatches in cost-optimized assignment tests + mypy `attr-defined` error in strategy registry test - Address all Greptile post-merge review findings across PRs #170–#175 (14 fixes total) ### PR #175 — Test assertion fixes (CI blockers) - `"no cost data"` → `"insufficient cost data"` to match implementation wording - `unknown-dev` → `known-dev` winner assertion (all-or-nothing fallback, sort stability) - `getattr()` for `_scorer` access on protocol type (Windows/Linux mypy difference) ### PR #174 — Workspace isolation - `_validate_git_ref` raises context-appropriate exception types (`WorkspaceMergeError` in merge, `WorkspaceCleanupError` in teardown) - `_run_git` catches `asyncio.CancelledError` to kill subprocess before re-raising (prevents orphaned git processes) ### PR #172 — Task assignment - `TaskAssignmentConfig.strategy` validated against 6 known strategy names - `max_concurrent_tasks_per_agent` enforced via new `AssignmentRequest.max_concurrent_tasks` field in `_score_and_filter_candidates` - `TaskAssignmentStrategy` protocol docstring documents error signaling contract (raises vs `selected=None`) ### PR #171 — Worktree skill - `rebase` uses `--left-right --count` with triple-dot to detect behind-main worktrees - `setup` reuse path uses `git worktree add` without `-b` for existing branches - `setup` handles dirty working tree with stash/abort prompt - `status` table shows both ahead and behind counts - `tree` provides circular dependency recovery guidance ### PR #170 — Meeting parsing - `assigned?` → `assigned` regex fix (prevents false-positive assignee extraction from "assign to X") ## Test plan - [x] All 3988 tests pass (10 new tests added) - [x] mypy strict: 0 errors (463 source files) - [x] ruff lint + format: all clean - [x] Coverage: 96.53% (threshold: 80%) - [x] Pre-commit hooks pass ## Review coverage Quick mode — automated checks only (lint, type-check, tests, coverage). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
🤖 I have created a release *beep* *boop* --- ## [0.1.1](ai-company-v0.1.0...ai-company-v0.1.1) (2026-03-10) ### Features * add autonomy levels and approval timeout policies ([#42](#42), [#126](#126)) ([#197](#197)) ([eecc25a](eecc25a)) * add CFO cost optimization service with anomaly detection, reports, and approval decisions ([#186](#186)) ([a7fa00b](a7fa00b)) * add code quality toolchain (ruff, mypy, pre-commit, dependabot) ([#63](#63)) ([36681a8](36681a8)) * add configurable cost tiers and subscription/quota-aware tracking ([#67](#67)) ([#185](#185)) ([9baedfa](9baedfa)) * add container packaging, Docker Compose, and CI pipeline ([#269](#269)) ([435bdfe](435bdfe)), closes [#267](#267) * add coordination error taxonomy classification pipeline ([#146](#146)) ([#181](#181)) ([70c7480](70c7480)) * add cost-optimized, hierarchical, and auction assignment strategies ([#175](#175)) ([ce924fa](ce924fa)), closes [#173](#173) * add design specification, license, and project setup ([8669a09](8669a09)) * add env var substitution and config file auto-discovery ([#77](#77)) ([7f53832](7f53832)) * add FastestStrategy routing + vendor-agnostic cleanup ([#140](#140)) ([09619cb](09619cb)), closes [#139](#139) * add HR engine and performance tracking ([#45](#45), [#47](#47)) ([#193](#193)) ([2d091ea](2d091ea)) * add issue auto-search and resolution verification to PR review skill ([#119](#119)) ([deecc39](deecc39)) * add memory retrieval, ranking, and context injection pipeline ([#41](#41)) ([873b0aa](873b0aa)) * add pluggable MemoryBackend protocol with models, config, and events ([#180](#180)) ([46cfdd4](46cfdd4)) * add pluggable MemoryBackend protocol with models, config, and events ([#32](#32)) ([46cfdd4](46cfdd4)) * add pluggable PersistenceBackend protocol with SQLite implementation ([#36](#36)) ([f753779](f753779)) * add progressive trust and promotion/demotion subsystems ([#43](#43), [#49](#49)) ([3a87c08](3a87c08)) * add retry handler, rate limiter, and provider resilience ([#100](#100)) ([b890545](b890545)) * add SecOps security agent with rule engine, audit log, and ToolInvoker integration ([#40](#40)) ([83b7b6c](83b7b6c)) * add shared org memory and memory consolidation/archival ([#125](#125), [#48](#48)) ([4a0832b](4a0832b)) * design unified provider interface ([#86](#86)) ([3e23d64](3e23d64)) * expand template presets, rosters, and add inheritance ([#80](#80), [#81](#81), [#84](#84)) ([15a9134](15a9134)) * implement agent runtime state vs immutable config split ([#115](#115)) ([4cb1ca5](4cb1ca5)) * implement AgentEngine core orchestrator ([#11](#11)) ([#143](#143)) ([f2eb73a](f2eb73a)) * implement basic tool system (registry, invocation, results) ([#15](#15)) ([c51068b](c51068b)) * implement built-in file system tools ([#18](#18)) ([325ef98](325ef98)) * implement communication foundation — message bus, dispatcher, and messenger ([#157](#157)) ([8e71bfd](8e71bfd)) * implement company template system with 7 built-in presets ([#85](#85)) ([cbf1496](cbf1496)) * implement conflict resolution protocol ([#122](#122)) ([#166](#166)) ([e03f9f2](e03f9f2)) * implement core entity and role system models ([#69](#69)) ([acf9801](acf9801)) * implement crash recovery with fail-and-reassign strategy ([#149](#149)) ([e6e91ed](e6e91ed)) * implement engine extensions — Plan-and-Execute loop and call categorization ([#134](#134), [#135](#135)) ([#159](#159)) ([9b2699f](9b2699f)) * implement enterprise logging system with structlog ([#73](#73)) ([2f787e5](2f787e5)) * implement graceful shutdown with cooperative timeout strategy ([#130](#130)) ([6592515](6592515)) * implement hierarchical delegation and loop prevention ([#12](#12), [#17](#17)) ([6be60b6](6be60b6)) * implement LiteLLM driver and provider registry ([#88](#88)) ([ae3f18b](ae3f18b)), closes [#4](#4) * implement LLM decomposition strategy and workspace isolation ([#174](#174)) ([aa0eefe](aa0eefe)) * implement meeting protocol system ([#123](#123)) ([ee7caca](ee7caca)) * implement message and communication domain models ([#74](#74)) ([560a5d2](560a5d2)) * implement model routing engine ([#99](#99)) ([d3c250b](d3c250b)) * implement parallel agent execution ([#22](#22)) ([#161](#161)) ([65940b3](65940b3)) * implement per-call cost tracking service ([#7](#7)) ([#102](#102)) ([c4f1f1c](c4f1f1c)) * implement personality injection and system prompt construction ([#105](#105)) ([934dd85](934dd85)) * implement single-task execution lifecycle ([#21](#21)) ([#144](#144)) ([c7e64e4](c7e64e4)) * implement subprocess sandbox for tool execution isolation ([#131](#131)) ([#153](#153)) ([3c8394e](3c8394e)) * implement task assignment subsystem with pluggable strategies ([#172](#172)) ([c7f1b26](c7f1b26)), closes [#26](#26) [#30](#30) * implement task decomposition and routing engine ([#14](#14)) ([9c7fb52](9c7fb52)) * implement Task, Project, Artifact, Budget, and Cost domain models ([#71](#71)) ([81eabf1](81eabf1)) * implement tool permission checking ([#16](#16)) ([833c190](833c190)) * implement YAML config loader with Pydantic validation ([#59](#59)) ([ff3a2ba](ff3a2ba)) * implement YAML config loader with Pydantic validation ([#75](#75)) ([ff3a2ba](ff3a2ba)) * initialize project with uv, hatchling, and src layout ([39005f9](39005f9)) * initialize project with uv, hatchling, and src layout ([#62](#62)) ([39005f9](39005f9)) * Litestar REST API, WebSocket feed, and approval queue (M6) ([#189](#189)) ([29fcd08](29fcd08)) * make TokenUsage.total_tokens a computed field ([#118](#118)) ([c0bab18](c0bab18)), closes [#109](#109) * parallel tool execution in ToolInvoker.invoke_all ([#137](#137)) ([58517ee](58517ee)) * testing framework, CI pipeline, and M0 gap fixes ([#64](#64)) ([f581749](f581749)) * wire all modules into observability system ([#97](#97)) ([f7a0617](f7a0617)) ### Bug Fixes * address Greptile post-merge review findings from PRs [#170](https://github.com/Aureliolo/ai-company/issues/170)-[#175](https://github.com/Aureliolo/ai-company/issues/175) ([#176](#176)) ([c5ca929](c5ca929)) * address post-merge review feedback from PRs [#164](https://github.com/Aureliolo/ai-company/issues/164)-[#167](https://github.com/Aureliolo/ai-company/issues/167) ([#170](#170)) ([3bf897a](3bf897a)), closes [#169](#169) * enforce strict mypy on test files ([#89](#89)) ([aeeff8c](aeeff8c)) * harden Docker sandbox, MCP bridge, and code runner ([#50](#50), [#53](#53)) ([d5e1b6e](d5e1b6e)) * harden git tools security + code quality improvements ([#150](#150)) ([000a325](000a325)) * harden subprocess cleanup, env filtering, and shutdown resilience ([#155](#155)) ([d1fe1fb](d1fe1fb)) * incorporate post-merge feedback + pre-PR review fixes ([#164](#164)) ([c02832a](c02832a)) * pre-PR review fixes for post-merge findings ([#183](#183)) ([26b3108](26b3108)) * strengthen immutability for BaseTool schema and ToolInvoker boundaries ([#117](#117)) ([7e5e861](7e5e861)) ### Performance * harden non-inferable principle implementation ([#195](#195)) ([02b5f4e](02b5f4e)), closes [#188](#188) ### Refactoring * adopt NotBlankStr across all models ([#108](#108)) ([#120](#120)) ([ef89b90](ef89b90)) * extract _SpendingTotals base class from spending summary models ([#111](#111)) ([2f39c1b](2f39c1b)) * harden BudgetEnforcer with error handling, validation extraction, and review fixes ([#182](#182)) ([c107bf9](c107bf9)) * harden personality profiles, department validation, and template rendering ([#158](#158)) ([10b2299](10b2299)) * pre-PR review improvements for ExecutionLoop + ReAct loop ([#124](#124)) ([8dfb3c0](8dfb3c0)) * split events.py into per-domain event modules ([#136](#136)) ([e9cba89](e9cba89)) ### Documentation * add ADR-001 memory layer evaluation and selection ([#178](#178)) ([db3026f](db3026f)), closes [#39](#39) * add agent scaling research findings to DESIGN_SPEC ([#145](#145)) ([57e487b](57e487b)) * add CLAUDE.md, contributing guide, and dev documentation ([#65](#65)) ([55c1025](55c1025)), closes [#54](#54) * add crash recovery, sandboxing, analytics, and testing decisions ([#127](#127)) ([5c11595](5c11595)) * address external review feedback with MVP scope and new protocols ([#128](#128)) ([3b30b9a](3b30b9a)) * expand design spec with pluggable strategy protocols ([#121](#121)) ([6832db6](6832db6)) * finalize 23 design decisions (ADR-002) ([#190](#190)) ([8c39742](8c39742)) * update project docs for M2.5 conventions and add docs-consistency review agent ([#114](#114)) ([99766ee](99766ee)) ### Tests * add e2e single agent integration tests ([#24](#24)) ([#156](#156)) ([f566fb4](f566fb4)) * add provider adapter integration tests ([#90](#90)) ([40a61f4](40a61f4)) ### CI/CD * add Release Please for automated versioning and GitHub Releases ([#278](#278)) ([a488758](a488758)) * bump actions/checkout from 4 to 6 ([#95](#95)) ([1897247](1897247)) * bump actions/upload-artifact from 4 to 7 ([#94](#94)) ([27b1517](27b1517)) * harden CI/CD pipeline ([#92](#92)) ([ce4693c](ce4693c)) * split vulnerability scans into critical-fail and high-warn tiers ([#277](#277)) ([aba48af](aba48af)) ### Maintenance * add /worktree skill for parallel worktree management ([#171](#171)) ([951e337](951e337)) * add design spec context loading to research-link skill ([8ef9685](8ef9685)) * add post-merge-cleanup skill ([#70](#70)) ([f913705](f913705)) * add pre-pr-review skill and update CLAUDE.md ([#103](#103)) ([92e9023](92e9023)) * add research-link skill and rename skill files to SKILL.md ([#101](#101)) ([651c577](651c577)) * bump aiosqlite from 0.21.0 to 0.22.1 ([#191](#191)) ([3274a86](3274a86)) * bump pyyaml from 6.0.2 to 6.0.3 in the minor-and-patch group ([#96](#96)) ([0338d0c](0338d0c)) * bump ruff from 0.15.4 to 0.15.5 ([a49ee46](a49ee46)) * fix M0 audit items ([#66](#66)) ([c7724b5](c7724b5)) * pin setup-uv action to full SHA ([#281](#281)) ([4448002](4448002)) * post-audit cleanup — PEP 758, loggers, bug fixes, refactoring, tests, hookify rules ([#148](#148)) ([c57a6a9](c57a6a9)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
🤖 I have created a release *beep* *boop* --- ## [0.1.0](v0.0.0...v0.1.0) (2026-03-11) ### Features * add autonomy levels and approval timeout policies ([#42](#42), [#126](#126)) ([#197](#197)) ([eecc25a](eecc25a)) * add CFO cost optimization service with anomaly detection, reports, and approval decisions ([#186](#186)) ([a7fa00b](a7fa00b)) * add code quality toolchain (ruff, mypy, pre-commit, dependabot) ([#63](#63)) ([36681a8](36681a8)) * add configurable cost tiers and subscription/quota-aware tracking ([#67](#67)) ([#185](#185)) ([9baedfa](9baedfa)) * add container packaging, Docker Compose, and CI pipeline ([#269](#269)) ([435bdfe](435bdfe)), closes [#267](#267) * add coordination error taxonomy classification pipeline ([#146](#146)) ([#181](#181)) ([70c7480](70c7480)) * add cost-optimized, hierarchical, and auction assignment strategies ([#175](#175)) ([ce924fa](ce924fa)), closes [#173](#173) * add design specification, license, and project setup ([8669a09](8669a09)) * add env var substitution and config file auto-discovery ([#77](#77)) ([7f53832](7f53832)) * add FastestStrategy routing + vendor-agnostic cleanup ([#140](#140)) ([09619cb](09619cb)), closes [#139](#139) * add HR engine and performance tracking ([#45](#45), [#47](#47)) ([#193](#193)) ([2d091ea](2d091ea)) * add issue auto-search and resolution verification to PR review skill ([#119](#119)) ([deecc39](deecc39)) * add mandatory JWT + API key authentication ([#256](#256)) ([c279cfe](c279cfe)) * add memory retrieval, ranking, and context injection pipeline ([#41](#41)) ([873b0aa](873b0aa)) * add pluggable MemoryBackend protocol with models, config, and events ([#180](#180)) ([46cfdd4](46cfdd4)) * add pluggable MemoryBackend protocol with models, config, and events ([#32](#32)) ([46cfdd4](46cfdd4)) * add pluggable output scan response policies ([#263](#263)) ([b9907e8](b9907e8)) * add pluggable PersistenceBackend protocol with SQLite implementation ([#36](#36)) ([f753779](f753779)) * add progressive trust and promotion/demotion subsystems ([#43](#43), [#49](#49)) ([3a87c08](3a87c08)) * add retry handler, rate limiter, and provider resilience ([#100](#100)) ([b890545](b890545)) * add SecOps security agent with rule engine, audit log, and ToolInvoker integration ([#40](#40)) ([83b7b6c](83b7b6c)) * add shared org memory and memory consolidation/archival ([#125](#125), [#48](#48)) ([4a0832b](4a0832b)) * design unified provider interface ([#86](#86)) ([3e23d64](3e23d64)) * expand template presets, rosters, and add inheritance ([#80](#80), [#81](#81), [#84](#84)) ([15a9134](15a9134)) * implement agent runtime state vs immutable config split ([#115](#115)) ([4cb1ca5](4cb1ca5)) * implement AgentEngine core orchestrator ([#11](#11)) ([#143](#143)) ([f2eb73a](f2eb73a)) * implement AuditRepository for security audit log persistence ([#279](#279)) ([94bc29f](94bc29f)) * implement basic tool system (registry, invocation, results) ([#15](#15)) ([c51068b](c51068b)) * implement built-in file system tools ([#18](#18)) ([325ef98](325ef98)) * implement communication foundation — message bus, dispatcher, and messenger ([#157](#157)) ([8e71bfd](8e71bfd)) * implement company template system with 7 built-in presets ([#85](#85)) ([cbf1496](cbf1496)) * implement conflict resolution protocol ([#122](#122)) ([#166](#166)) ([e03f9f2](e03f9f2)) * implement core entity and role system models ([#69](#69)) ([acf9801](acf9801)) * implement crash recovery with fail-and-reassign strategy ([#149](#149)) ([e6e91ed](e6e91ed)) * implement engine extensions — Plan-and-Execute loop and call categorization ([#134](#134), [#135](#135)) ([#159](#159)) ([9b2699f](9b2699f)) * implement enterprise logging system with structlog ([#73](#73)) ([2f787e5](2f787e5)) * implement graceful shutdown with cooperative timeout strategy ([#130](#130)) ([6592515](6592515)) * implement hierarchical delegation and loop prevention ([#12](#12), [#17](#17)) ([6be60b6](6be60b6)) * implement LiteLLM driver and provider registry ([#88](#88)) ([ae3f18b](ae3f18b)), closes [#4](#4) * implement LLM decomposition strategy and workspace isolation ([#174](#174)) ([aa0eefe](aa0eefe)) * implement meeting protocol system ([#123](#123)) ([ee7caca](ee7caca)) * implement message and communication domain models ([#74](#74)) ([560a5d2](560a5d2)) * implement model routing engine ([#99](#99)) ([d3c250b](d3c250b)) * implement parallel agent execution ([#22](#22)) ([#161](#161)) ([65940b3](65940b3)) * implement per-call cost tracking service ([#7](#7)) ([#102](#102)) ([c4f1f1c](c4f1f1c)) * implement personality injection and system prompt construction ([#105](#105)) ([934dd85](934dd85)) * implement single-task execution lifecycle ([#21](#21)) ([#144](#144)) ([c7e64e4](c7e64e4)) * implement subprocess sandbox for tool execution isolation ([#131](#131)) ([#153](#153)) ([3c8394e](3c8394e)) * implement task assignment subsystem with pluggable strategies ([#172](#172)) ([c7f1b26](c7f1b26)), closes [#26](#26) [#30](#30) * implement task decomposition and routing engine ([#14](#14)) ([9c7fb52](9c7fb52)) * implement Task, Project, Artifact, Budget, and Cost domain models ([#71](#71)) ([81eabf1](81eabf1)) * implement tool permission checking ([#16](#16)) ([833c190](833c190)) * implement YAML config loader with Pydantic validation ([#59](#59)) ([ff3a2ba](ff3a2ba)) * implement YAML config loader with Pydantic validation ([#75](#75)) ([ff3a2ba](ff3a2ba)) * initialize project with uv, hatchling, and src layout ([39005f9](39005f9)) * initialize project with uv, hatchling, and src layout ([#62](#62)) ([39005f9](39005f9)) * Litestar REST API, WebSocket feed, and approval queue (M6) ([#189](#189)) ([29fcd08](29fcd08)) * make TokenUsage.total_tokens a computed field ([#118](#118)) ([c0bab18](c0bab18)), closes [#109](#109) * parallel tool execution in ToolInvoker.invoke_all ([#137](#137)) ([58517ee](58517ee)) * testing framework, CI pipeline, and M0 gap fixes ([#64](#64)) ([f581749](f581749)) * wire all modules into observability system ([#97](#97)) ([f7a0617](f7a0617)) ### Bug Fixes * address Greptile post-merge review findings from PRs [#170](https://github.com/Aureliolo/ai-company/issues/170)-[#175](https://github.com/Aureliolo/ai-company/issues/175) ([#176](#176)) ([c5ca929](c5ca929)) * address post-merge review feedback from PRs [#164](https://github.com/Aureliolo/ai-company/issues/164)-[#167](https://github.com/Aureliolo/ai-company/issues/167) ([#170](#170)) ([3bf897a](3bf897a)), closes [#169](#169) * enforce strict mypy on test files ([#89](#89)) ([aeeff8c](aeeff8c)) * harden Docker sandbox, MCP bridge, and code runner ([#50](#50), [#53](#53)) ([d5e1b6e](d5e1b6e)) * harden git tools security + code quality improvements ([#150](#150)) ([000a325](000a325)) * harden subprocess cleanup, env filtering, and shutdown resilience ([#155](#155)) ([d1fe1fb](d1fe1fb)) * incorporate post-merge feedback + pre-PR review fixes ([#164](#164)) ([c02832a](c02832a)) * pre-PR review fixes for post-merge findings ([#183](#183)) ([26b3108](26b3108)) * resolve circular imports, bump litellm, fix release tag format ([#286](#286)) ([a6659b5](a6659b5)) * strengthen immutability for BaseTool schema and ToolInvoker boundaries ([#117](#117)) ([7e5e861](7e5e861)) ### Performance * harden non-inferable principle implementation ([#195](#195)) ([02b5f4e](02b5f4e)), closes [#188](#188) ### Refactoring * adopt NotBlankStr across all models ([#108](#108)) ([#120](#120)) ([ef89b90](ef89b90)) * extract _SpendingTotals base class from spending summary models ([#111](#111)) ([2f39c1b](2f39c1b)) * harden BudgetEnforcer with error handling, validation extraction, and review fixes ([#182](#182)) ([c107bf9](c107bf9)) * harden personality profiles, department validation, and template rendering ([#158](#158)) ([10b2299](10b2299)) * pre-PR review improvements for ExecutionLoop + ReAct loop ([#124](#124)) ([8dfb3c0](8dfb3c0)) * split events.py into per-domain event modules ([#136](#136)) ([e9cba89](e9cba89)) ### Documentation * add ADR-001 memory layer evaluation and selection ([#178](#178)) ([db3026f](db3026f)), closes [#39](#39) * add agent scaling research findings to DESIGN_SPEC ([#145](#145)) ([57e487b](57e487b)) * add CLAUDE.md, contributing guide, and dev documentation ([#65](#65)) ([55c1025](55c1025)), closes [#54](#54) * add crash recovery, sandboxing, analytics, and testing decisions ([#127](#127)) ([5c11595](5c11595)) * address external review feedback with MVP scope and new protocols ([#128](#128)) ([3b30b9a](3b30b9a)) * expand design spec with pluggable strategy protocols ([#121](#121)) ([6832db6](6832db6)) * finalize 23 design decisions (ADR-002) ([#190](#190)) ([8c39742](8c39742)) * update project docs for M2.5 conventions and add docs-consistency review agent ([#114](#114)) ([99766ee](99766ee)) ### Tests * add e2e single agent integration tests ([#24](#24)) ([#156](#156)) ([f566fb4](f566fb4)) * add provider adapter integration tests ([#90](#90)) ([40a61f4](40a61f4)) ### CI/CD * add Release Please for automated versioning and GitHub Releases ([#278](#278)) ([a488758](a488758)) * bump actions/checkout from 4 to 6 ([#95](#95)) ([1897247](1897247)) * bump actions/upload-artifact from 4 to 7 ([#94](#94)) ([27b1517](27b1517)) * bump anchore/scan-action from 6.5.1 to 7.3.2 ([#271](#271)) ([80a1c15](80a1c15)) * bump docker/build-push-action from 6.19.2 to 7.0.0 ([#273](#273)) ([dd0219e](dd0219e)) * bump docker/login-action from 3.7.0 to 4.0.0 ([#272](#272)) ([33d6238](33d6238)) * bump docker/metadata-action from 5.10.0 to 6.0.0 ([#270](#270)) ([baee04e](baee04e)) * bump docker/setup-buildx-action from 3.12.0 to 4.0.0 ([#274](#274)) ([5fc06f7](5fc06f7)) * bump sigstore/cosign-installer from 3.9.1 to 4.1.0 ([#275](#275)) ([29dd16c](29dd16c)) * harden CI/CD pipeline ([#92](#92)) ([ce4693c](ce4693c)) * split vulnerability scans into critical-fail and high-warn tiers ([#277](#277)) ([aba48af](aba48af)) ### Maintenance * add /worktree skill for parallel worktree management ([#171](#171)) ([951e337](951e337)) * add design spec context loading to research-link skill ([8ef9685](8ef9685)) * add post-merge-cleanup skill ([#70](#70)) ([f913705](f913705)) * add pre-pr-review skill and update CLAUDE.md ([#103](#103)) ([92e9023](92e9023)) * add research-link skill and rename skill files to SKILL.md ([#101](#101)) ([651c577](651c577)) * bump aiosqlite from 0.21.0 to 0.22.1 ([#191](#191)) ([3274a86](3274a86)) * bump pyyaml from 6.0.2 to 6.0.3 in the minor-and-patch group ([#96](#96)) ([0338d0c](0338d0c)) * bump ruff from 0.15.4 to 0.15.5 ([a49ee46](a49ee46)) * fix M0 audit items ([#66](#66)) ([c7724b5](c7724b5)) * **main:** release ai-company 0.1.1 ([#282](#282)) ([2f4703d](2f4703d)) * pin setup-uv action to full SHA ([#281](#281)) ([4448002](4448002)) * post-audit cleanup — PEP 758, loggers, bug fixes, refactoring, tests, hookify rules ([#148](#148)) ([c57a6a9](c57a6a9)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Signed-off-by: Aurelio <19254254+Aureliolo@users.noreply.github.com>
Summary
TaskAssignmentStrategyprotocolTaskAssignmentServiceorchestrator that validates task status and delegates to a pluggable strategyAssignmentRequest,AssignmentResult,AssignmentCandidate,AgentWorkloadwith model validators (_validate_collections,_validate_selected_not_in_alternatives)TaskAssignmentConfigadded toRootConfigschema withstrategy,min_score,max_concurrent_tasks_per_agentobservability.events.task_assignmentTest plan
Review coverage
Pre-reviewed by 10 agents (code-reviewer, python-reviewer, pr-test-analyzer, silent-failure-hunter, comment-analyzer, type-design-analyzer, logging-audit, resilience-audit, security-reviewer, docs-consistency). 25 findings triaged and addressed.
🤖 Generated with Claude Code
Closes #26
Closes #30