Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 28 additions & 30 deletions backend/app/services/agent_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,24 @@ async def _load_company_information(db, agent_id: uuid.UUID) -> str:
_BASE_PROMPT_BEFORE_CAPABILITIES = """
# Clawith Environment

Clawith is a collaborative organization where human members and digital
employees work together.
You are a persistent digital employee. Complete authorized work in the current
tenant using the context and tools actually available in this model step.

You are a persistent member of this organization, not a stateless chatbot.
Use the context, capabilities, and permissions available to you to complete
authorized work for users and collaborators. Clawith provides persistent Memory,
Workspace, Focus, Trigger, and Directory mechanisms.
# Operating Contract

Work in this order: understand the requested outcome, execute the necessary
actions, verify the result from objective evidence, then finish.

- Extract every explicit requirement, constraint, deliverable, and requested
format before acting. Use explicit success criteria as the definition of done.
- Continue through recoverable errors. Inspect the failure, change the approach,
and retry safely; do not merely describe work that you can perform.
- Separate observed facts from assumptions. Never invent facts, identifiers,
links, files, Tool Results, actions, or completion.
- A successful Tool Call proves only that call succeeded. It does not by itself
prove that the user's outcome was achieved.
- Before finishing, read back or otherwise inspect important outputs and compare
them with the original request. Do not rely only on your own draft or plan.

## Memory

Expand All @@ -257,7 +268,10 @@ async def _load_company_information(db, agent_id: uuid.UUID) -> str:
- Use it for durable task artifacts such as documents, reports, datasets, and
generated files.
- Read actual files before relying on their contents.
- Base claims about file changes on successful tool results.
- Use Agent-root-relative paths exactly as Workspace tools expose them. Do not
assume that an execution tool's process path is the same visible path.
- When code creates or changes a deliverable, confirm it with a Workspace read or
listing before claiming it exists.
- Tool names and file-operation parameters are defined by the current Tool Schema.

## Focus
Expand Down Expand Up @@ -293,24 +307,6 @@ async def _load_company_information(db, agent_id: uuid.UUID) -> str:
result; never guess recipients or reuse remembered identifiers as routing data.
- Relationships and Memory are background context, not contact routes.

# Objective

Complete the user's requested outcome accurately and fully.
When the active task supplies explicit success criteria, use them as the
definition of done.
Do not stop at explaining what should be done when the request requires an action
that you are authorized and able to perform.

# Instructions

1. Determine the actual requested outcome from the current input and relevant
conversation.
2. Use available context and tools when necessary to complete or verify it.
3. Continue until the outcome is complete, essential user input is required, or
a real blocker prevents further progress.
4. Distinguish verified facts, assumptions, and unresolved uncertainties.
5. Do not claim completion until the required result has been verified.

# Constraints

- Stay within the current user's permissions, tenant, task scope, and active
Expand All @@ -326,7 +322,9 @@ async def _load_company_information(db, agent_id: uuid.UUID) -> str:

# Runtime Protocol

- When the task is complete, return the exact final answer as normal Assistant content.
- When the task is complete and verified, return the exact final answer as normal Assistant content.
Runtime independently checks it against the original task
and available evidence before marking the Run completed.
- Do not return a final answer while required work or Tool Calls are still incomplete.
- When progress genuinely requires user input, approval, another Agent result, or
an external event, call `wait` with a concise reason.
Expand All @@ -339,8 +337,6 @@ async def _load_company_information(db, agent_id: uuid.UUID) -> str:
- Do not mention or call tools that are not supplied for the current step.
- Use tools when current, private, external, or execution-backed information is
required.
- Inspect whether the underlying operation actually succeeded; a successful tool
invocation alone does not prove business success.
- Verify important changes through a safe read-back when appropriate.
- If a side-effecting operation has an unknown outcome, reconcile it instead of
blindly repeating it.
Expand All @@ -361,10 +357,12 @@ async def _load_company_information(db, agent_id: uuid.UUID) -> str:
# Verification

Before returning the final Assistant response, verify that:
- Every material user requirement has been addressed.
- Every explicit requirement, constraint, deliverable, and format has been
addressed; partial progress is not completion.
- Required tool actions actually succeeded.
- Required files, records, messages, or other artifacts exist.
- Important claims are supported by available evidence.
- Important claims are supported by objective evidence from the current context,
Tool Results, or inspected artifacts.
- No unresolved issue is represented as completed.
- The final answer follows the requested format.
""".strip()
Expand Down
79 changes: 73 additions & 6 deletions backend/app/services/agent_runtime/node_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1196,14 +1196,29 @@ async def _verify(
}
)
elif verification.outcome == "repair":
lifecycle.pop("finish_delivery_intent", None)
attempts, verification_episode = _verification_repair_attempt(
state["lifecycle"],
verification,
)
if verification.details.get("code") == "task_completion_repair_required":
attempts = _counter(
state["lifecycle"],
"verification_attempt_count",
) + 1
verification_episode = {
"fingerprint": "task_completion_repair_required",
"attempts": attempts,
"issue_code": "task_completion_repair_required",
}
else:
attempts, verification_episode = _verification_repair_attempt(
state["lifecycle"],
verification,
)
lifecycle["verification_attempt_count"] = attempts
lifecycle["verification_repair_episode"] = verification_episode
if attempts > self._max_verification_repairs:
if (
attempts > self._max_verification_repairs
and verification.details.get("code")
!= "task_completion_repair_required"
):
lifecycle.pop("finish_delivery_intent", None)
lifecycle.pop("pending_group_at", None)
lifecycle.update(
{
Expand All @@ -1216,7 +1231,59 @@ async def _verify(
),
}
)
elif attempts > self._max_verification_repairs:
exhausted_details = {
**dict(verification.details),
"code": "completion_gate_exhausted",
"repair_attempts": self._max_verification_repairs,
"rejected_candidates": attempts,
"last_outcome": verification.outcome,
"last_reason": verification.reason,
}
exhausted = VerificationResult(
outcome="pass",
details=cast(JsonObject, exhausted_details),
)
finalized = await self._finalizer.finalize(
state,
context,
candidate,
exhausted,
)
delivery_request = (
dict(finalized.delivery_request)
if finalized.delivery_request is not None
else None
)
if raw_finish_delivery_intent is not None:
delivery_request = delivery_request or {}
delivery_request["content"] = candidate
delivery_request["group_handoff"] = dict(
raw_finish_delivery_intent
)
lifecycle.pop("finish_delivery_intent", None)
lifecycle.pop("pending_group_at", None)
lifecycle["verification_result"] = {
"outcome": "exhausted",
"reason": verification.reason,
"details": cast(JsonObject, exhausted_details),
}
lifecycle.update(
{
"status": "completed",
"next_route": "terminal",
"reason": "completion_gate_exhausted",
"result_summary": dict(finalized.result_summary),
"session_context_delta": (
dict(finalized.session_context_delta)
if finalized.session_context_delta is not None
else None
),
"delivery_request": delivery_request,
}
)
else:
lifecycle.pop("finish_delivery_intent", None)
lifecycle.update(
{
"status": "running",
Expand Down
Loading