Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Infrastructure & Operations11 min read

Adaptive Case Management: A Practical Wiki-Style Guide for Technical Teams

A working reference for adaptive case management (ACM): the mental model, a runnable case-state implementation, verification, and the tradeoffs versus BPM.

TL;DR

Adaptive case management (ACM) is a way of coordinating knowledge-intensive work where the path to an outcome is decided by a case worker at runtime rather than fixed in advance. It is also called dynamic case management, and older material groups it under advanced case management. This guide gives you a durable mental model, a runnable case-state implementation you can adapt, a verification procedure, and the tradeoffs that decide when ACM beats a structured workflow engine. It is written as a reference you can return to, not a product shortlist.

What adaptive case management means in practice

Adaptive case management (ACM) coordinates a service request — a claim, an investigation, an onboarding, a citizen or patient matter — by treating the case as a container of goals, data, and available actions rather than a fixed sequence of tasks. The case worker chooses the next action based on context and judgment; the system records what happened, enforces permissions, and keeps the case auditable. The same concept appears in older sources as dynamic case management or advanced case management, and the Wikipedia-style framing usually stresses coordination across finance, health, legal, citizen, and HR matters.

The practical distinction is the question being asked. A structured process asks "how should this be done?" and encodes the answer as a path. ACM asks "what should be done next, given this case?" and encodes the answer as a set of permitted actions plus the state needed to choose among them. That shift is why ACM fits unpredictable, knowledge-intensive work and why it is a poor fit for high-volume, repeatable transactions.

This article is a technical reference: it defines the model, shows a minimal implementation, and describes how to verify that the implementation actually behaves adaptively. It deliberately does not recommend a specific vendor; if you need a concrete hosting or endpoint decision for the infrastructure behind such a system, route that question to RPC Assistant rather than treating this page as a provider list.

  • ACM = goals + case data + permitted actions + audit trail, with runtime human choice.
  • Synonyms you will encounter: dynamic case management, advanced case management.
  • Best fit: exception-heavy, judgment-driven, low-to-moderate volume work.
  • Poor fit: deterministic, high-volume, fully specified transactions.

Assumptions and prerequisites before you build

The implementation below assumes you can run Python 3.10 or newer, that you have a place to persist case state (a database, a document store, or even a JSON file for a prototype), and that you have an identity layer that can attribute every action to a named user. ACM without attribution is just an unstructured inbox; the audit trail is the feature.

You should also decide up front which parts of the work are genuinely structured. Most real deployments are hybrids: a few mandatory steps (identity verification, regulatory checks) are enforced, while the investigative or resolution work is adaptive. Treating the whole case as unstructured throws away useful controls; treating the whole case as a fixed flow defeats the purpose.

Finally, agree on a case schema. At minimum you need a case identifier, a goal or outcome definition, a status, a set of artifacts (documents, notes, messages), a set of available actions, and an append-only event log. If your team cannot agree on those six things, the tooling choice is premature.

The mental model: goals, state, actions, and events

Think of a case as a small state machine whose transitions are proposed by humans and validated by the system. The goal is a predicate over case data — for example, "claim adjudicated and payment authorized." The state is the current set of facts and artifacts. Actions are the operations a worker may attempt. Events are the immutable record of what was attempted and what resulted.

This model maps cleanly onto CMMN (Case Management Model and Notation), which describes cases as a set of tasks, milestones, and sentries that become available based on conditions rather than a fixed sequence. You do not need a CMMN engine to benefit from the model; you need the discipline of separating what is permitted from what is required.

A useful test for whether your design is truly adaptive: can a competent worker complete a valid case without following the same order of actions as the previous case? If the answer is no, you have built a workflow with extra steps, not ACM.

  • Goal: a predicate that defines completion, not a task list.
  • State: facts and artifacts the worker and system can read.
  • Actions: permitted operations, gated by role and state.
  • Events: append-only log used for audit, replay, and metrics.

Runnable implementation: a minimal adaptive case engine

The following Python module implements the core loop: define a case, list available actions for the current state, apply an action, and record an event. It is intentionally small so you can read it end to end, and it uses only the standard library. Replace the in-memory store with your database of choice before using it anywhere real.

Note the two guardrails that make it adaptive rather than chaotic: actions declare a precondition over case data, and every applied action appends an event with the actor and timestamp. The engine never decides the order; it only refuses actions whose preconditions are unmet.

from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Callable

@dataclass
class Case:
    case_id: str
    goal: str
    data: dict = field(default_factory=dict)
    events: list = field(default_factory=list)
    closed: bool = False

@dataclass
class Action:
    name: str
    precondition: Callable[[Case], bool]
    effect: Callable[[Case], None]

class CaseEngine:
    def __init__(self, actions: list[Action]):
        self.actions = {a.name: a for a in actions}

    def available(self, case: Case) -> list[str]:
        if case.closed:
            return []
        return [a.name for a in self.actions.values() if a.precondition(case)]

    def apply(self, case: Case, action_name: str, actor: str) -> None:
        action = self.actions.get(action_name)
        if action is None:
            raise ValueError(f"unknown action: {action_name}")
        if not action.precondition(case):
            raise PermissionError(f"precondition failed for {action_name}")
        action.effect(case)
        case.events.append({
            "action": action_name,
            "actor": actor,
            "at": datetime.now(timezone.utc).isoformat(),
            "data_snapshot": dict(case.data),
        })

# Example wiring for an insurance-style case
verify = Action(
    name="verify_identity",
    precondition=lambda c: "identity_verified" not in c.data,
    effect=lambda c: c.data.update(identity_verified=True),
)
request_docs = Action(
    name="request_documents",
    precondition=lambda c: c.data.get("identity_verified") is True,
    effect=lambda c: c.data.update(documents_requested=True),
)
adjudicate = Action(
    name="adjudicate",
    precondition=lambda c: c.data.get("documents_requested") is True,
    effect=lambda c: c.data.update(adjudicated=True),
)
close = Action(
    name="close_case",
    precondition=lambda c: c.data.get("adjudicated") is True,
    effect=lambda c: setattr(c, "closed", True),
)

engine = CaseEngine([verify, request_docs, adjudicate, close])
case = Case(case_id="C-1001", goal="claim adjudicated and closed")
print(engine.available(case))          # ['verify_identity']
engine.apply(case, "verify_identity", "worker@example.com")
print(engine.available(case))          # ['request_documents']

Exposing the case engine over HTTP for integration

Most teams will not call the engine directly; they will expose it through an API so a case-worker UI, an automation service, and an audit consumer can all interact with the same state. The example below uses FastAPI and keeps the same guardrails. It is a sketch, not a production service: add authentication, persistence, and idempotency keys before relying on it.

The important design choice is that the API exposes "available actions" as a first-class endpoint. That lets the UI render only what is currently permitted, which is how ACM systems stay usable as case complexity grows.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()
CASES: dict[str, Case] = {}

class CreateCase(BaseModel):
    case_id: str
    goal: str

class ApplyAction(BaseModel):
    action: str
    actor: str

@app.post("/cases")
def create_case(body: CreateCase):
    CASES[body.case_id] = Case(case_id=body.case_id, goal=body.goal)
    return {"case_id": body.case_id}

@app.get("/cases/{case_id}/actions")
def list_actions(case_id: str):
    case = CASES.get(case_id)
    if not case:
        raise HTTPException(status_code=404)
    return {"available": engine.available(case)}

@app.post("/cases/{case_id}/actions")
def apply_action(case_id: str, body: ApplyAction):
    case = CASES.get(case_id)
    if not case:
        raise HTTPException(status_code=404)
    try:
        engine.apply(case, body.action, body.actor)
    except PermissionError as exc:
        raise HTTPException(status_code=409, detail=str(exc))
    return {"events": len(case.events), "closed": case.closed}

Verification procedure and results table

Verification for an ACM system is not a single pass/fail test; it is a set of properties you check against recorded cases. Run the procedure below against a sample of real or synthetic cases and record the results in a table. Do not fill in numbers you have not measured — the table is a template for your own run.

Procedure: (1) export the event log for at least 30 cases; (2) for each case, confirm that every applied action had a satisfied precondition at the time it was applied; (3) confirm that no case reached a closed state without its goal predicate being true; (4) count how many distinct action orderings occurred across cases — a healthy adaptive system shows variety; (5) count precondition rejections and inspect whether they cluster around a confusing action. The table below is the shape to fill in; the values are placeholders you replace with your measurements.

  • Property 1 — precondition integrity: every applied action was permitted at apply time.
  • Property 2 — goal integrity: no closed case violates its goal predicate.
  • Property 3 — ordering variety: distinct action sequences across the sample.
  • Property 4 — rejection clustering: rejections grouped by action name.
  • Property 5 — audit completeness: every event has actor and timestamp.
| Property | Sample size | Pass count | Fail count | Notes |
|---|---|---|---|---|
| Precondition integrity | <N cases> | <measured> | <measured> | <where failures occurred> |
| Goal integrity | <N cases> | <measured> | <measured> | <goal predicate used> |
| Ordering variety | <N cases> | <distinct sequences> | n/a | <most common sequence> |
| Rejection clustering | <N events> | n/a | <measured> | <top rejected action> |
| Audit completeness | <N events> | <measured> | <measured> | <missing fields> |

Expected verification output and how to read it

A healthy run should show zero precondition-integrity failures and zero goal-integrity failures; any nonzero value there is a bug in the engine or in how actions were applied out of band. Ordering variety should be greater than one for genuinely adaptive work — if every case follows the same sequence, you have effectively built a workflow and should consider a BPM engine instead.

Rejection clustering is the most useful diagnostic. A high rejection count on one action usually means the precondition is stricter than the work requires, or the UI is offering an action that is rarely valid. Both are design signals, not necessarily defects.

Audit completeness should be 100%. If events are missing actors or timestamps, stop and fix the write path before adding features; an ACM system without a trustworthy log cannot be audited or improved.

Troubleshooting common ACM implementation problems

The failures below account for most early-stage ACM problems. Each has a concrete first check.

If workers report that the system "won't let me do anything," inspect the available-actions endpoint for a representative case and compare it to the preconditions in code. The usual cause is a precondition that depends on data written by a step the worker skipped, which is a modeling error rather than a bug.

If cases stall indefinitely, you likely have no escalation or aging mechanism. ACM assumes human judgment, but it still needs time-based triggers — a case untouched for N days should surface to a supervisor. Add that as an event-driven job, not as a hard-coded step.

If the audit log disagrees with what workers remember doing, check for out-of-band writes: direct database edits, admin tools, or batch imports that bypass the engine. Route every mutation through the action API, or you lose the property that makes ACM auditable.

  • No available actions: precondition depends on skipped data — review the case schema.
  • Stalled cases: add aging and escalation triggers outside the action set.
  • Audit mismatch: find and close out-of-band write paths.
  • Too many rejections: loosen or clarify the precondition, or hide the action in the UI.

ACM versus BPM: choosing the right control model

Business Process Management (BPM) encodes a known sequence and optimizes for consistency and throughput. ACM encodes permitted actions and optimizes for judgment and exception handling. The two are complementary: a common pattern is a BPM-managed intake that creates a case, an ACM-managed resolution phase, and a BPM-managed closure and payment step.

Choose BPM when the path is known, volume is high, and deviation is an error. Choose ACM when the path depends on facts that only emerge during the work, volume is moderate, and deviation is expected. If you cannot articulate the goal as a predicate, you are not ready for ACM — you are still discovering the process.

For teams running the infrastructure behind either model, the operational concerns overlap: reliable state storage, authenticated APIs, and observable event streams. If your case system depends on chain data or on-chain identity, the endpoint reliability question belongs with RPC Assistant; start from the multi-chain RPC endpoints guide and the RPC endpoints guide for the general model, then use RPC pricing and dedicated nodes when you need to size capacity.

Limitations, tradeoffs, and governance

ACM trades predictability for fit. That tradeoff has costs: harder testing, harder capacity planning, and a greater dependence on worker skill. You cannot regression-test an adaptive system the way you test a fixed workflow, so you invest in property checks and sampled review instead.

Governance is the other cost. Because workers can choose actions, you need clear role definitions, a review process for precondition changes, and retention rules for the event log. In regulated domains — healthcare, legal, financial services — the audit trail is often the primary compliance artifact, so treat its schema as a contract.

Finally, be honest about volume. ACM tooling and review overhead do not amortize well on very high-volume, low-variance work. If most of your cases follow the same path, the adaptive layer is cost without benefit.

  • Harder to test: rely on property checks and sampled review.
  • Harder to plan capacity: model by case arrival and handling time, not step counts.
  • Governance required: roles, change review, retention policy.
  • Volume ceiling: reconsider if most cases share one path.

Next steps: from prototype to production

Move from the in-memory prototype to a persisted store with an append-only event table, then add authentication so every action carries a verified actor. Next, instrument the available-actions endpoint and the rejection counts; those two signals tell you whether the model matches the work.

When the case system touches blockchain infrastructure — for example, anchoring audit events or verifying on-chain identity — treat endpoint selection as a separate, measurable decision. The API service overview and the supported networks list are the right starting points, and the blockchain node hosting guide covers the operational side. For concrete endpoint and provider choices, ask RPC Assistant rather than guessing from a list.

Finally, schedule a review after the first hundred cases. Compare ordering variety and rejection clustering against your baseline; if variety collapses, you have drifted toward BPM and should either simplify the model or adopt a workflow engine deliberately.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started